Two Responsibilities in the .NET TPL Scheduler Class

I like parallel programming.  Most of my experience comes from developing on Windows in C/C++ and C#.  I haven’t yet had the pleasure of doing so functionally (which I plan to correct).

I prefer task/job based parallelism using threadpools rather than explicit threads performing a suite of tasks (such as FIOS or Kinect).  I could delve into this further, but that is not the purpose of this post.

The Task Parallel Library hinted at an opportunity to build logic I have always wanted with a job based implementation.  I wanted to be able to define my own schedules for task execution.  Unfortunately there is a flaw.

The Scheduler class has two responsibilities.  The Scheduler class not only handles scheduling of tasks, but their execution as well.  Combining these two responsibilities inhibits scheduler composition beyond inheritance and encapsulation.  Furthermore any external libraries that create tasks based on their own schedulers cannot have the scheduling of their tasks altered by any other schedulers.

What I would like to see is the separation of the Scheduler logic from execution.  Ideally a Task could have multiple Schedulers applied representing multiple scheduling constraints in addition to the existing dependency constraints.  To provide additional Scheduler constraints to classes across libraries I would like to see a pattern similar to one a (excellent!) colleague at BioWare arrived at for a different problem of global scope.   At global scope have a single global Scheduler class that will apply itself to new Task instances that are created.  At thread local scope have a stack of Schedulers that can be pushed and popped in the thread context to apply themselves to Tasks created in the thread.  Tasks created within a given thread local context also inherit that context (so any new Tasks spawned by the initial Task use the same stack of Schedulers).

Finally execution needs to be separated out into its own class.  Furthermore it needs to be extensible–able to add different execution targets beyond the CPU, such as GPU, CUDA, SPUs, IO, et cetera.  Tasks can execute in a subset of execution targets (no reason the assembly couldn’t contain multiple representations of the same code for different targets or possibly JIT it) and Schedulers only apply to certain execution targets.

Then you could build a critical path scheduler (based on the dependency chain and accumulated stats of execution time of tasks) for all tasks being executed.

This is not a trivial change, risking breaking substantial code already in the wild.  I would be inclined to approach it as partitioning the logic internally for the Scheduler class into an ISchedulerConstraint and an IExecutionTarget interface with the updated Scheduler class implementing both.  Then provide a default global ISchedulerConstraint and IExecutionTarget implementation that is consistent with current functionality.  I believe this would let existing code continue to work while providing a vector for more sophisticated task scheduling.

Leave a Reply

Your email address will not be published. Required fields are marked *