Friday, July 3, 2009

Tasks, TaskScheduler and new Thread Pool in .Net 4.0

Another item in list of newly added threading primitives in .Net 4.0 is a Task. It represents asynchronous operation which execution is controlled by TaskScheduler. In this post I will try to analyze task and scheduler capabilities.


First of all lets look how can we use Task. For this propose I changed some code from previous post to use Task instead of thread pool mechanism:

  1. public void TestBarrier()
  2. {
  3. Barrier barrier = new Barrier(3, b =>
  4. {
  5. Console.WriteLine("Phase {0} completed", b.CurrentPhaseNumber);
  6. });
  7. Task task1 = Task.Factory.StartNew(() => Worker(new WorkerInfo { WorkTime = 400, Name = "First", ParticipiantPhaces = 4, Barrier = barrier }));
  8. Task task2 = Task.Factory.StartNew(() => Worker(new WorkerInfo { WorkTime = 600, Name = "Second", ParticipiantPhaces = 3, Barrier = barrier }));
  9. Task task3 = Task.Factory.StartNew(() => Worker(new WorkerInfo { WorkTime = 500, Name = "Third", ParticipiantPhaces = 2, Barrier = barrier }));
  10. Task.WaitAll(task1, task2, task3);
  11. Console.WriteLine("Barrier done.");
  12. }

Lines 8-10 creates and schedules three tasks each of them will execute Worker function. First difference is that task is initialized by Action or Func<TResult> delegates in opposite to thread pool WaitCallBack delegate. It means that there are two types of tasks: first one initialized by Action that does it work and does not returns any values, and another one initialized by Func<TResult> that will return some value. Thus, if task is initialized by Func<TResult> new instance of Task<TResult> is created which contains Result property. When task is finished value returned by Func delegate will be stored in the task and can be obtained from Result property. Both types of tasks contains Exception property that will store an exception thrown by the worker delegates. If exception is not thrown Exception property will return null.


Next important tasks feature is synchronization. Task contains two static methods WaitAll and WaitAny that acts exactly like WaitHandle.WaitAll and WaitHandle.WaitAny. That means that there is built-in tasks synchronization mechanism. Line 12 shows how it is simple to block until all executed functions are finished.


So, let’s understand what happens when task is started, by StartNew function for example. Task execution is controlled by TaskScheduler associated to current synchronization context. Actually task scheduler is an abstract class and it is up to its implementation to decide how task will be executed.

protected internal abstract void QueueTask(Task task)

When new task is started task scheduler QueueTask method is called. Now scheduler have to decide what to do. Default scheduler will execute tasks in same manner as thread pool did it before. But now task scheduler can be replaced by user implementation. There is good sample in MSDN, showing dedicated thread pool implementation which executes all queued tasks turn by turn in one thread.


Task provides very comfortable and tunable parallel programming mechanism. However it is important to note that user can’t assume that Task he executes will really run in parallel. That's why it is very bad idea to block task execution in any way, i.e. lock or wait. The best practice to use task for any independent operation that doesn’t need to access any shared resources.