Why Tasks?

  • Limitation of Threadpool QueueUserWorkItem
    • No Built in way to know when operation is completed.
    • No way to return values

Tasks are introduced to overcome the above limitations. We need to note down below points to properly understand tasks.

  1. The Scheduler may use the current thread to execute the task
  2. Unhandled exception swallowed
    • Wait / Result will throw System.AggregateException
    • .NET 4 TaskScheduler.UnobservedTaskException
    • .NET 4.5 App Config <ThrowUnobservedTaskExceptions enabled="true"/>
  3. Wait on Array of Tasks:
    WaitAny to wait on array of tasks returns index / cancellation exception
    WaitAll to wait on array of tasks returns true for success / false for time out
  4. Task Schedulers:
    Schedules all tasks onto GUI thread using FromCurrentSynchronizationContext

Examples

Task Start

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression. 
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.", 
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.

Task Creation Options

// TaskCreationOptions: Specifies flags that control optional behavior for the creation and execution of tasks
var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Task Continuation

using System;
using System.Threading.Tasks;

public class Example
{
   public static async Task Main()
   {
      // Execute the antecedent.
      Task<DayOfWeek> taskA = Task.Run( () => DateTime.Today.DayOfWeek );

      // Execute the continuation when the antecedent finishes.
      await taskA.ContinueWith( antecedent => Console.WriteLine("Today is {0}.", antecedent.Result) );
   }
}
// The example displays output like the following output:
//       Today is Monday.

Parent Child relationship

An attached child Task however is one which has a special relationship with the parent:

  1. The parent waits for the child to finish before it completes
  2. If the child Task throws an exception then the parent will catch it and re-throw it
  3. The status of the parent depends on the status of the child
Task parentTask = Task.Factory.StartNew(() =>
{   
        Console.WriteLine("Starting child task...");            
    Task childTask = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Child running. Going to sleep for a sec.");
        Thread.Sleep(1000);
        Console.WriteLine("Child finished and throws an exception.");
        throw new Exception();
    }, TaskCreationOptions.AttachedToParent);               
});
 
try
{
    Console.WriteLine("Waiting for parent task");
    parentTask.Wait();
    Console.WriteLine("Parent task finished");
}
catch (AggregateException ex)
{
    Console.WriteLine("Exception: {0}", ex.InnerException.GetType());
}

Dealing with UI Thread and Task

Below code causes Cross Threading Exception

private void TestButton_Click(object sender, EventArgs e)
{
    TestButton.Enabled = false;
 
    var backgroundTask = new Task(() =>
    {
        Thread.Sleep(5000);
        TestButton.Enabled = true;  // Causes cross-threading exception
    });
    backgroundTask.Start();
}

Below code uses FromCurrentSynchronizationContext to use Tasks for the GUI Thread

private void TestButton_Click(object sender, EventArgs e)
{
    TestButton.Enabled = false;
    var uiThreadScheduler = TaskScheduler.FromCurrentSynchronizationContext();
 
    var backgroundTask = new Task(() =>
    {
        Thread.Sleep(5000);
    });
 
    var uiTask = backgroundTask.ContinueWith(t =>
    {
        TestButton.Enabled = true;
    }, uiThreadScheduler);
 
    backgroundTask.Start();
}

References

TaskCreationOptions Enum
How to: Chain Multiple Tasks with Continuations
Creating an attached child Task in .NET C#
TaskContinuationOptions Enum
Attaching Continuation Tasks to the UI Thread