任务调度
CCR第三个重要的组成部分是任务调度:当有接收器的port上有消息到达时如何生成任务和在有多个执行资源的机器上进行负载均衡。在CCR中有三个实现或者抽象任务调度的重要类。
- ITask接口和它的实现:Task类和IterativeTask类。只有实现了ITask接口的类型才能被CCR调度。仲裁器也实现了ITask接口,所以他们可以被调度和正确的激活。
- DispatcherQueue类型。DispatcherQueue是FIFO的任务队列,它可以用CLR线程池来调度任务(非常少见)或者用一个CCR Dispatcher的实例来调度。
- Dispatcher类型。Dispatcher管理操作系统线程和对从一个或者多个DispatcherQueue中取出的任务进行负载均衡。
例16.
0, // zero means use one thread per CPU, or 2 if only one CPU present
"sample dispatcher" // friendly name assgined to OS threads
);
var taskQueue = new DispatcherQueue(
"sample queue", // friendly name
dispatcher // dispatcher instance
);
var port = new Port<int>();
Arbiter.Activate(taskQueue,
Arbiter.Receive(
true,
port,
item => Console.WriteLine(item)
)
);
// post item, so delegate executes
port.Post(5);
例16展示了如何创建Dispatcher和DispatcherQueue实例并用于调度任务。下面是对这个例子的每一步的描述:
- 使用0作为线程数量参数创建一个Dispatcher,这使得CCR根据机器的CPU数量自己选择一个线程数量。每个CPU上的线程数量是根据Dispatcher的静态属性ThreadsPerCpu确定的,默认为每个CPU一个线程。
- 使用步骤1中创建的Dispatcher作为参数创建一个DispatcherQueue实例,这一步将DispatcherQueue附加到Dispatcher上。
- 创建一个Port<int>类型的实例。我们将用这个port来投递元素和附加带有delegate点接收器。
- 使用下列参数调用Arbiter.Activate方法。a.面创建的DispatcherQueue实例;b.用前面创建的port和一个回调作为参数新创建的一个接收者仲裁器,回调将会在有元素被投递到port时被调用。
- 一个int类型的元素被投递到port。
当一个元素被投递到附加了接收器的port,port的实现中将会发生如下操作:
- 为投递进来的元素创建一个容器。容器的类型(IPortElement)允许CCR在不知道元素类型的情况下将元素排队并将元素赋值给Task实例。
- 容器被放入队列。
- 如果接收器列表不是null,并且其中有一个以上的接收器,port对象将会调用ReceiverTask.Evaluate方法来让接收器和它里面的仲裁器层次检测元素是否可以被使用,在这个例子中,Evaluate方法将会返回true,并使用收到的元素和用户的delegate作为参数创建一个Task<int>实例。
- port使用调用Evaluate方法返回的Task对象作为参数调用taskQueue.Enqueue,注意,当一个接收器是第一次被激活,它会被关联到由Arbiter.Activate方法提供的DispatcherQueue实例。
当上面的4步完成之后,生成的Task对象现在已经被调度逻辑分发(dealt)。
例17
taskQueue.Enqueue(
new Task<int>(5, item => Console.WriteLine(item))
);
上面这个例子我们展示了一个与例16中的调度功能等价的代码,但是不需要向port中投递任何元素。当数据已经准备好而且代码可以立即来处理它时,直接创建Task的实例很有用。CCR在Port.Post被调用时会调用接收器来创建Task实例,创建操作和例子中的类似。
一旦一个元素被放入DispatcherQueue,将会做如下操作:
- DispatcherQueue向它所属的Dispatcher发信号,告诉Dispatcher一个新的任务可以被执行了。
- Dispatcher通知一个或者多个TaskExecutionWorker类型对象。每个TaskExecutionWorker对象管理一个操作系统线程。它将线程设置到一种高效的休眠状态,直到Dispatcher发出信号通知有元素可以被调度时。
- TaskExecutionWorker对象调用DispatcherQueue.Test方法从队列中获取一个任务。如果可用的任务,TaskExecutionWorker对象调用ITask.Execute。
- Task.Execute方法调用关联在task对象上的delegate,并将一个或者多个关联在task上的参数传递进去。在我们的例子中一个值为5的单参数被传递给delegate,delegate将参数输出到控制台。
速度控制
CCR DispatcherQueue的实现允许根据一些预先定义的规则对任务执行进行速度控制。任务速度控制是CCR的关键功能,它使得程序可以把管理大队列的复杂性交给CCR调度器,从而优雅的处理大量的消息负载。速度控制策略是在DispatcherQueue创建时指定的。因为每个协调原语可以使用不同的DispatcherQueue,所以程序员可以给不同的处理器(handler)应用不同的策略。
任务执行策略枚举
/// Specifies dispatcher queue task scheduling behavior
/// </summary>
public enum TaskExecutionPolicy
{
/// <summary>
/// Default behavior, all tasks are queued with no constraints
/// </summary>
Unconstrained = 0,
/// <summary>
/// Queue enforces maximum depth (specified at queue creation)
/// and discards tasks enqueued after the limit is reached
/// </summary>
ConstrainQueueDepthDiscardTasks,
/// <summary>
/// Queue enforces maximum depth (specified at queue creation)
/// but does not discard anny tasks. It forces the thread posting any tasks after the limit is reached, to
/// sleep until the queue depth falls below the limit
/// </summary>
ConstrainQueueDepthThrottleExecution,
/// <summary>
/// Queue enforces the rate of task scheduling specified at queue creation
/// and discards tasks enqueued after the current scheduling rate is above the specified rate
/// </summary>
ConstrainSchedulingRateDiscardTasks,
/// <summary>
/// Queue enforces the rate of task scheduling specified at queue creation
/// and forces the thread posting tasks to sleep until the current rate of task scheduling falls below
/// the specified average rate
/// </summary>
ConstrainSchedulingRateThrottleExecution
}
例26.
{
int maximumDepth = 10;
Dispatcher dispatcher = new Dispatcher(0, "throttling example");
DispatcherQueue depthThrottledQueue = new DispatcherQueue("ConstrainQueueDepthDiscard",
dispatcher,
TaskExecutionPolicy.ConstrainQueueDepthDiscardTasks,
maximumDepth);
Port<int> intPort = new Port<int>();
Arbiter.Activate(depthThrottledQueue,
Arbiter.Receive(true, intPort,
delegate(int i)
{
// only some items will be received since throttling will discard most of them
Console.WriteLine(i);
})
);
// post items as fast as possible so that the depth policy is activated and discards
// all the oldest items in the dispatcher queue
for (int i = 0; i < maximumDepth * 100000; i++)
{
intPort.Post(i);
}
}
例26中,我们创建了一个Dispatcher实例和一个DispatcherQueue实例,但是我们指定了一个ConstrainQueueDepthDiscardTasks任务执行策略,ConstrainQueueDepthDiscardTasks是一个策略选项。然后我们附加了一个持久化的接收器,并且以最快的速度投递了100万个元素。如果没有指定策略,一百万个任务将会被调度到机器的所有CPU上,并且dispatcher queue的深度将会增长到非常大。当指定了策略的时候,CCR将会抛弃最旧的任务,而且只会保持最新的10个任务执行。这种情况在只有最新的消息是有效的情况下是很有用的(通知、定时器等等)。
重要:depthThrottledQueue实例是提供给Arbiter.Activate方法将这个队列和指定的策略关联在一起,并将队列和用户在收到元素时生成Task对象的一个单元素接收器关联在一起。
策略场景
- ConstrainSchedulingRateDiscardTasks: 适用于CCR处理器希望处理的消息是以某个均匀的速度到达的情况,例如每秒多少条消息。保存所有的消息并不重要,重要的是保持最近的消息。该策略保证代码以固定的速率执行,即使是突然到来大量的消息。适用于传感器通知、定时器事件等。
- ConstrainQueueDepthDiscardTasks:适用于消息可已被取消,但是最近N条消息必须被保留的情况。如果CPU执行任务落后于消息生成任务,该策略保证最老的消息被丢弃,而最近的N个消息被安排执行。最有用的深度数值是1,因为它保持了最近的一条消息。这个策略对于处理紧急消息很有用,例如传感器数据、超时的定时器、任何没有已知平均速率的消息和非周期性到达的消息。
- ConstrainSchedulingRateThrottleExecution: 仅适用于周期性消息的源头是同一个操作系统进程中的另外一个线程的情况。速率调节将在接收器和Dispatcher Queue关联的Post方法的调用者中引入Thread.Sleep方法。这将减慢消息创建的速度。没有任务会被取消。
- ConstrainQueueDepthThrottleExecution:与上一个策略的行为相同,除了它适用于消息以随即的间隔突发到达,没有周期性规律的情况。这种情况对于从网络中的其他机器上接收消息的ports来说很常见。同样没有消息会被丢弃,但是执行网络接收操作的线程会被减速,降低投递消息到CCR port的速度。
posted on 2007-12-12 18:03 iceboundrock 阅读(833) 评论(2) 编辑 收藏
