달력

3

« 2025/3 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2017. 4. 22. 20:19

Task-based Asynchronous Programming 프로그래밍/C#2017. 4. 22. 20:19


  1. Task-based Asynchronous Programming
  2. Chaining Tasks by Using Continuation Tasks
  3. Attached and Detached Child Tasks
  4. Task Cancellation
  5. Exception Handling (Task Parallel Library)
  6. How to: Use Parallel.Invoke to Execute Parallel Operations
  7. How to: Return a Value from a Task
  8. How to: Cancel a Task and Its Children
  9. How to: Create Pre-Computed Tasks
  10. How to: Traverse a Binary Tree with Parallel Tasks
  11. How to: Unwrap a Nested Task
  12. How to: Prevent a Child Task from Attaching to its Parent


https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx



Task-based Asynchronous Programming


The Task Parallel Library (TPL) is based on the concept of a task, which represents an asynchronous operation. In some ways, a task resembles a thread or ThreadPool work item, but at a higher level of abstraction. The term task parallelism refers to one or more independent tasks running concurrently. Tasks provide two primary benefits:

  • More efficient and more scalable use of system resources.

    Behind the scenes, tasks are queued to the ThreadPool, which has been enhanced with algorithms that determine and adjust to the number of threads and that provide load balancing to maximize throughput. This makes tasks relatively lightweight, and you can create many of them to enable fine-grained parallelism.

  • More programmatic control than is possible with a thread or work item.

    Tasks and the framework built around them provide a rich set of APIs that support waiting, cancellation, continuations, robust exception handling, detailed status, custom scheduling, and more.

For both of these reasons, in the .NET Framework, TPL is the preferred API for writing multi-threaded, asynchronous, and parallel code.

The Parallel.Invoke method provides a convenient way to run any number of arbitrary statements concurrently. Just pass in an Action delegate for each item of work. The easiest way to create these delegates is to use lambda expressions. The lambda expression can either call a named method or provide the code inline. The following example shows a basic Invoke call that creates and starts two tasks that run concurrently. The first task is represented by a lambda expression that calls a method named DoSomeWork, and the second task is represented by a lambda expression that calls a method named DoSomeOtherWork.

System_CAPS_ICON_note.jpg Note

This documentation uses lambda expressions to define delegates in TPL. If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda Expressions in PLINQ and TPL.

            Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

System_CAPS_ICON_note.jpg Note

The number of Task instances that are created behind the scenes by Invoke is not necessarily equal to the number of delegates that are provided. The TPL may employ various optimizations, especially with large numbers of delegates.

For more information, see How to: Use Parallel.Invoke to Execute Parallel Operations.

For greater control over task execution or to return a value from the task, you have to work with Task objects more explicitly.

A task that does not return a value is represented by the System.Threading.Tasks.Task class. A task that returns a value is represented by the System.Threading.Tasks.Task<TResult> class, which inherits from Task. The task object handles the infrastructure details and provides methods and properties that are accessible from the calling thread throughout the lifetime of the task. For example, you can access the Status property of a task at any time to determine whether it has started running, ran to completion, was canceled, or has thrown an exception. The status is represented by a TaskStatus enumeration.

When you create a task, you give it a user delegate that encapsulates the code that the task will execute. The delegate can be expressed as a named delegate, an anonymous method, or a lambda expression. Lambda expressions can contain a call to a named method, as shown in the following example. Note that the example includes a call to the Task.Wait method to ensure that the task completes execution before the console mode application ends.

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.

You can also use the Task.Run methods to create and start a task in one operation. To manage the task, the Run methods use the default task scheduler, regardless of which task scheduler is associated with the current thread. The Run methods are the preferred way to create and start tasks when more control over the creation and scheduling of the task is not needed.

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

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

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // 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.

You can also use the TaskFactory.StartNew method to create and start a task in one operation. Use this method when creation and scheduling do not have to be separated and you require additional task creation options or the use of a specific scheduler, or when you need to pass additional state into the task through its AsyncState property, as shown in the following example.

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

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

      // Better: Create and start the task in one operation. 
      Task taskA = Task.Factory.StartNew(() => Console.WriteLine("Hello from taskA."));
      
      // 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 and Task<TResult> each expose a static Factory property that returns a default instance of TaskFactory, so that you can call the method as Task.Factory.StartNew(). Also, in the following example, because the tasks are of type System.Threading.Tasks.Task<TResult>, they each have a public Task<TResult>.Result property that contains the result of the computation. The tasks run asynchronously and may complete in any order. If the Result property is accessed before the computation finishes, the property blocks the calling thread until the value is available.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)), 
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;
        
        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i], 
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum; 
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0

For more information, see How to: Return a Value from a Task.

When you use a lambda expression to create a delegate, you have access to all the variables that are visible at that point in your source code. However, in some cases, most notably within loops, a lambda doesn't capture the variable as expected. It only captures the final value, not the value as it mutates after each iteration. The following example illustrates the problem. It passes a loop counter to a lambda expression that instantiates a CustomData object and uses the loop counter as the object's identifier. As the output from the example shows, each CustomData object has an identical identifier.

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

class CustomData
{
   public long CreationTime;
   public int Name; 
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}; 
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);     
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.

You can access the value on each iteration by providing a state object to a task through its constructor. The following example modifies the previous example by using the loop counter when creating the CustomData object, which, in turn, is passed to the lambda expression. As the output from the example shows, each CustomData object now has a unique identifier based on the value of the loop counter at the time the object was instantiated.

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

class CustomData
{
   public long CreationTime;
   public int Name; 
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop. 
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null) 
                                                     return;
                                     
                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);     
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.

This state is passed as an argument to the task delegate, and it can be accessed from the task object by using the Task.AsyncState property. The following example is a variation on the previous example. It uses the AsyncState property to display information about the CustomData objects passed to the lambda expression.

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

class CustomData
{
   public long CreationTime;
   public int Name; 
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null) 
                                                     return;
                                     
                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);     
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }                     
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.

Every task receives an integer ID that uniquely identifies it in an application domain and can be accessed by using the Task.Id property. The ID is useful for viewing task information in the Visual Studio debugger Parallel Stacks and Tasks windows. The ID is lazily created, which means that it isn't created until it is requested; therefore, a task may have a different ID every time the program is run. For more information about how to view task IDs in the debugger, see Using the Tasks Window and Using the Parallel Stacks Window.

Most APIs that create tasks provide overloads that accept a TaskCreationOptions parameter. By specifying one of these options, you tell the task scheduler how to schedule the task on the thread pool. The following table lists the various task creation options.

TaskCreationOptions parameter valueDescription
NoneThe default when no option is specified. The scheduler uses its default heuristics to schedule the task.
PreferFairnessSpecifies that the task should be scheduled so that tasks created sooner will be more likely to be executed sooner, and tasks created later will be more likely to execute later.
LongRunningSpecifies that the task represents a long-running operation.
AttachedToParentSpecifies that a task should be created as an attached child of the current task, if one exists. For more information, see Attached and Detached Child Tasks.
DenyChildAttachSpecifies that if an inner task specifies the AttachedToParent option, that task will not become an attached child task.
HideSchedulerSpecifies that the task scheduler for tasks created by calling methods like TaskFactory.StartNew or Task<TResult>.ContinueWith from within a particular task is the default scheduler instead of the scheduler on which this task is running.

The options may be combined by using a bitwise OR operation. The following example shows a task that has the LongRunning and PreferFairness option.

            var task3 = new Task(() => MyLongRunningMethod(),
                                TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
            task3.Start();

Each thread has an associated culture and UI culture, which is defined by the Thread.CurrentCulture and Thread.CurrentUICulture properties, respectively. A thread's culture is used in such operations as formatting, parsing, sorting, and string comparison. A thread's UI culture is used in resource lookup. Ordinarily, unless you specify a default culture for all the threads in an application domain by using the CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture properties, the default culture and UI culture of a thread is defined by the system culture. If you explicitly set a thread's culture and launch a new thread, the new thread does not inherit the culture of the calling thread; instead, its culture is the default system culture. The task-based programming model for apps that target versions of the .NET Framework prior to .NET Framework 4.6 adhere to this practice.

System_CAPS_ICON_important.jpg Important

Note that the calling thread's culture as part of a task's context applies to apps that target the .NET Framework 4.6, not apps that run under the .NET Framework 4.6. You can target a particular version of the .NET Framework when you create your project in Visual Studio by selecting that version from the dropdown list at the top of the New Project dialog box, or outside of Visual Studio you can use the TargetFrameworkAttribute attribute. For apps that target versions of the .NET Framework prior to the .NET Framework 4.6, or that do not target a specific version of the .NET Framework, a task's culture continues to be determined by the culture of the thread on which it runs.

Starting with apps that target the .NET Framework 4.6, the calling thread's culture is inherited by each task, even if the task runs asynchronously on a thread pool thread.

The following example provides a simple illustration. It uses the TargetFrameworkAttribute attribute to target the .NET Framework 4.6 and changes the app's current culture to either French (France) or, if French (France) is already the current culture, English (United States). It then invokes a delegate named formatDelegate that returns some numbers formatted as currency values in the new culture. Note that whether the delegate as a task either synchronously or asynchronously, it returns the expected result because the culture of the calling thread is inherited by the asynchronous task.

using System;
using System.Globalization;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;

[assembly:TargetFramework(".NETFramework,Version=v4.6")]

public class Example
{
   
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));
                                                   
                                             output += Environment.NewLine;
                                             return output;
                                           };
       
       Console.WriteLine("The example is running on thread {0}", 
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}", 
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);
       
       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());
       
       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:"); 
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);
       
       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate); 
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163�025�412,32 �   18�905�365,59 �
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163�025�412,32 �   18�905�365,59 �
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163�025�412,32 �   18�905�365,59 �
// If the TargetFrameworkAttribute statement is removed, the example
// displays the following output:
//          The example is running on thread 1
//          The current culture is en-US
//          Changed the current culture to fr-FR.
//
//          Executing the delegate synchronously:
//          Formatting using the fr-FR culture on thread 1.
//          163�025�412,32 ?   18�905�365,59 ?
//
//          Executing a task asynchronously:
//          Formatting using the en-US culture on thread 3.
//          $163,025,412.32   $18,905,365.59
//
//          Executing a task synchronously:
//          Formatting using the fr-FR culture on thread 1.
//          163�025�412,32 ?   18�905�365,59 ?

If you are using Visual Studio, you can omit the TargetFrameworkAttribute attribute and instead select the .NET Framework 4.6 as the target when you create the project in the New Project dialog.

For output that reflects the behavior of apps the target versions of the .NET Framework prior to .NET Framework 4.6, remove the TargetFrameworkAttribute attribute from the source code. The output will reflect the formatting conventions of the default system culture, not the culture of the calling thread.

For more information on asynchronous tasks and culture, see the "Culture and asynchronous task-based operations" section in the CultureInfo topic.

The Task.ContinueWith and Task<TResult>.ContinueWith methods let you specify a task to start when the antecedent task finishes. The delegate of the continuation task is passed a reference to the antecedent task so that it can examine the antecedent task's status and, by retrieving the value of the Task<TResult>.Result property, can use the output of the antecedent as input for the continuation.

In the following example, the getData task is started by a call to the TaskFactory.StartNew<TResult>(Func<TResult>) method. The processData task is started automatically when getData finishes, and displayData is started when processData finishes. getData produces an integer array, which is accessible to the processData task through the getData task's Task<TResult>.Result property. The processData task processes that array and returns a result whose type is inferred from the return type of the lambda expression passed to the Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult>) method. The displayData task executes automatically when processData finishes, and the Tuple<T1, T2, T3> object returned by the processData lambda expression is accessible to the displayData task through the processData task's Task<TResult>.Result property. The displayData task takes the result of the processData task and produces a result whose type is inferred in a similar manner and which is made available to the program in the Result property.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {                         
      var getData = Task.Factory.StartNew(() => { 
                                             Random rnd = new Random(); 
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );  
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;
                                  
                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } ); 
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2, 
                                                                         x.Result.Item3);
                                                 } );                         
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Because Task.ContinueWith is an instance method, you can chain method calls together instead of instantiating a Task<TResult> object for each antecedent task. The following example is functionally identical to the previous example, except that it chains together calls to the Task.ContinueWith method. Note that the Task<TResult> object returned by the chain of method calls is the final continuation task.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {                         
      var displayData = Task.Factory.StartNew(() => { 
                                                 Random rnd = new Random(); 
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).  
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;
                                  
                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ). 
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2, 
                                                             x.Result.Item3);
                                     } );                         
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

The ContinueWhenAll and ContinueWhenAny methods enable you to continue from multiple tasks.

For more information, see Chaining Tasks by Using Continuation Tasks.

When user code that is running in a task creates a new task and does not specify the AttachedToParent option, the new task is not synchronized with the parent task in any special way. This type of non-synchronized task is called a detached nested task or detached child task. The following example shows a task that creates one detached child task.

            var outer = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Outer task beginning.");

                var child = Task.Factory.StartNew(() =>
                {
                    Thread.SpinWait(5000000);
                    Console.WriteLine("Detached task completed.");
                });

            });

            outer.Wait();
            Console.WriteLine("Outer task completed.");
            // The example displays the following output:
            //    Outer task beginning.
            //    Outer task completed.
            //    Detached task completed.

Note that the parent task does not wait for the detached child task to finish.

When user code that is running in a task creates a task with the AttachedToParent option, the new task is known as a attached child task of the parent task. You can use the AttachedToParent option to express structured task parallelism, because the parent task implicitly waits for all attached child tasks to finish. The following example shows a parent task that creates ten attached child tasks. Note that although the example calls the Task.Wait method to wait for the parent task to finish, it does not have to explicitly wait for the attached child tasks to complete.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.", 
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.

A parent task can use the TaskCreationOptions.DenyChildAttach option to prevent other tasks from attaching to the parent task. For more information, see Attached and Detached Child Tasks.

The System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types provide several overloads of the Task.Wait and Task<TResult>.Wait methods that enable you to wait for a task to finish. In addition, overloads of the static Task.WaitAll and Task.WaitAny methods let you wait for any or all of an array of tasks to finish.

Typically, you would wait for a task for one of these reasons:

  • The main thread depends on the final result computed by a task.

  • You have to handle exceptions that might be thrown from the task.

  • The application may terminate before all tasks have completed execution. For example, console applications will terminate as soon as all synchronous code in Main (the application entry point) has executed.

The following example shows the basic pattern that does not involve exception handling.

            Task[] tasks = new Task[3]
            {
                Task.Factory.StartNew(() => MethodA()),
                Task.Factory.StartNew(() => MethodB()),
                Task.Factory.StartNew(() => MethodC())
            };

            //Block until all tasks complete.
            Task.WaitAll(tasks);

            // Continue on this thread...

For an example that shows exception handling, see Exception Handling.

Some overloads let you specify a time-out, and others take an additional CancellationToken as an input parameter, so that the wait itself can be canceled either programmatically or in response to user input.

When you wait for a task, you implicitly wait for all children of that task that were created by using the TaskCreationOptions.AttachedToParent option. Task.Wait returns immediately if the task has already completed. Any exceptions raised by a task will be thrown by a Task.Wait method, even if the Task.Wait method was called after the task completed.

The Task and Task<TResult> classes provide several methods that can help you compose multiple tasks to implement common patterns and to better use the asynchronous language features that are provided by C#, Visual Basic, and F#. This section describes the WhenAllWhenAnyDelay, and FromResult<TResult> methods.

Task.WhenAll

The Task.WhenAll method asynchronously waits for multiple Task or Task<TResult> objects to finish. It provides overloaded versions that enable you to wait for non-uniform sets of tasks. For example, you can wait for multiple Task and Task<TResult> objects to complete from one method call.

Task.WhenAny

The Task.WhenAny method asynchronously waits for one of multiple Task or Task<TResult> objects to finish. As in the Task.WhenAll method, this method provides overloaded versions that enable you to wait for non-uniform sets of tasks. The WhenAny method is especially useful in the following scenarios.

  • Redundant operations. Consider an algorithm or operation that can be performed in many ways. You can use the WhenAny method to select the operation that finishes first and then cancel the remaining operations.

  • Interleaved operations. You can start multiple operations that must all finish and use the WhenAny method to process results as each operation finishes. After one operation finishes, you can start one or more additional tasks.

  • Throttled operations. You can use the WhenAny method to extend the previous scenario by limiting the number of concurrent operations.

  • Expired operations. You can use the WhenAny method to select between one or more tasks and a task that finishes after a specific time, such as a task that is returned by the Delay method. The Delay method is described in the following section.

Task.Delay

The Task.Delay method produces a Task object that finishes after the specified time. You can use this method to build loops that occasionally poll for data, introduce time-outs, delay the handling of user input for a predetermined time, and so on.

Task(T).FromResult

By using the Task.FromResult<TResult> method, you can create a Task<TResult> object that holds a pre-computed result. This method is useful when you perform an asynchronous operation that returns a Task<TResult> object, and the result of that Task<TResult> object is already computed. For an example that uses FromResult<TResult> to retrieve the results of asynchronous download operations that are held in a cache, see How to: Create Pre-Computed Tasks.

When a task throws one or more exceptions, the exceptions are wrapped in an AggregateException exception. That exception is propagated back to the thread that joins with the task, which is typically the thread that is waiting for the task to finish or the thread that accesses the Result property. This behavior serves to enforce the .NET Framework policy that all unhandled exceptions by default should terminate the process. The calling code can handle the exceptions by using any of the following in a try/catch block:

The joining thread can also handle exceptions by accessing the Exception property before the task is garbage-collected. By accessing this property, you prevent the unhandled exception from triggering the exception propagation behavior that terminates the process when the object is finalized.

For more information about exceptions and tasks, see Exception Handling.

The Task class supports cooperative cancellation and is fully integrated with the System.Threading.CancellationTokenSource and System.Threading.CancellationToken classes, which were introduced in the .NET Framework 4. Many of the constructors in the System.Threading.Tasks.Task class take a CancellationToken object as an input parameter. Many of the StartNew and Run overloads also include a CancellationToken parameter.

You can create the token, and issue the cancellation request at some later time, by using the CancellationTokenSource class. Pass the token to the Task as an argument, and also reference the same token in your user delegate, which does the work of responding to a cancellation request.

For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

The TaskFactory class provides static methods that encapsulate some common patterns for creating and starting tasks and continuation tasks.

The default TaskFactory can be accessed as a static property on the Task class or Task<TResult> class. You can also instantiate a TaskFactory directly and specify various options that include a CancellationToken, a TaskCreationOptions option, a TaskContinuationOptions option, or a TaskScheduler. Whatever options are specified when you create the task factory will be applied to all tasks that it creates, unless the Task is created by using the TaskCreationOptions enumeration, in which case the task's options override those of the task factory.

In some cases, you may want to use a Task to encapsulate some asynchronous operation that is performed by an external component instead of your own user delegate. If the operation is based on the Asynchronous Programming Model Begin/End pattern, you can use the FromAsync methods. If that is not the case, you can use the TaskCompletionSource<TResult> object to wrap the operation in a task and thereby gain some of the benefits of Task programmability, for example, support for exception propagation and continuations. For more information, see TaskCompletionSource<TResult>.

Most application or library developers do not care which processor the task runs on, how it synchronizes its work with other tasks, or how it is scheduled on the System.Threading.ThreadPool. They only require that it execute as efficiently as possible on the host computer. If you require more fine-grained control over the scheduling details, the Task Parallel Library lets you configure some settings on the default task scheduler, and even lets you supply a custom scheduler. For more information, see TaskScheduler.

The TPL has several new public types that are useful in both parallel and sequential scenarios. These include several thread-safe, fast and scalable collection classes in the System.Collections.Concurrent namespace, and several new synchronization types, for example, System.Threading.Semaphore and System.Threading.ManualResetEventSlim, which are more efficient than their predecessors for specific kinds of workloads. Other new types in the .NET Framework 4, for example, System.Threading.Barrier and System.Threading.SpinLock, provide functionality that was not available in earlier releases. For more information, see Data Structures for Parallel Programming.

We recommend that you do not inherit from System.Threading.Tasks.Task or System.Threading.Tasks.Task<TResult>. Instead, we recommend that you use the AsyncState property to associate additional data or state with a Task or Task<TResult> object. You can also use extension methods to extend the functionality of the Task and Task<TResult> classes. For more information about extension methods, see Extension Methods and Extension Methods.

If you must inherit from Task or Task<TResult>, you cannot use RunRun<TResult>, or the System.Threading.Tasks.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult>, or System.Threading.Tasks.TaskCompletionSource<TResult> classes to create instances of your custom task type because these mechanisms create only Task and Task<TResult> objects. In addition, you cannot use the task continuation mechanisms that are provided by TaskTask<TResult>TaskFactory, and TaskFactory<TResult> to create instances of your custom task type because these mechanisms also create only Task and Task<TResult> objects.

TitleDescription
Chaining Tasks by Using Continuation TasksDescribes how continuations work.
Attached and Detached Child TasksDescribes the difference between attached and detached child tasks.
Task CancellationDescribes the cancellation support that is built into the Task object.
Exception HandlingDescribes how exceptions on concurrent threads are handled.
How to: Use Parallel.Invoke to Execute Parallel OperationsDescribes how to use Invoke.
How to: Return a Value from a TaskDescribes how to return values from tasks.
How to: Cancel a Task and Its ChildrenDescribes how to cancel tasks.
How to: Create Pre-Computed TasksDescribes how to use the Task.FromResult<TResult> method to retrieve the results of asynchronous download operations that are held in a cache.
How to: Traverse a Binary Tree with Parallel TasksDescribes how to use tasks to traverse a binary tree.
How to: Unwrap a Nested TaskDemonstrates how to use the Unwrap extension method.
Data ParallelismDescribes how to use For and ForEach<TSource, TLocal> to create parallel loops over data.
Parallel ProgrammingTop level node for .NET Framework parallel programming.






Chaining Tasks by Using Continuation Tasks


In asynchronous programming, it is very common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. Traditionally, this has been done by using callback methods. In the Task Parallel Library, the same functionality is provided by continuation tasks. A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent finishes.

Continuations are relatively easy to use, but are nevertheless very powerful and flexible. For example, you can:

  • Pass data from the antecedent to the continuation.

  • Specify the precise conditions under which the continuation will be invoked or not invoked.

  • Cancel a continuation either before it starts or cooperatively as it is running.

  • Provide hints about how the continuation should be scheduled.

  • Invoke multiple continuations from the same antecedent.

  • Invoke one continuation when all or any one of multiple antecedents complete.

  • Chain continuations one after another to any arbitrary length.

  • Use a continuation to handle exceptions thrown by the antecedent.

A continuation is a task that is created in the WaitingForActivation state. It is activated automatically when its antecedent task or tasks complete. Calling Task.Start on a continuation in user code throws an System.InvalidOperationException exception.

A continuation is itself a Task and does not block the thread on which it is started. Call the Task.Wait method to block until the continuation task finishes.

You create a continuation that executes when its antecedent has completed by calling the Task.ContinueWith method. The following example shows the basic pattern, (for clarity, exception handling is omitted). It executes an antecedent task, taskA, that returns a DayOfWeek object that indicates the name of the current day of the week. When the antecedent finishes, the continuation task, taskB, is passed the antecedent and displays a string that includes its result.

using System;
using System.Threading.Tasks;

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

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

You can also create a continuation that will run when any or all of a group of tasks has completed. To execute a continuation when all antecedent tasks have completed, you call the static (Shared in Visual Basic) Task.WhenAll method or the instance TaskFactory.ContinueWhenAll method. To execute a continuation when any of the antecedent tasks has completed, you call the static (Shared in Visual Basic) Task.WhenAny method or the instance TaskFactory.ContinueWhenAny method.

Note that calls to the Task.WhenAll and Task.WhenAny overloads do not block the calling thread. However, you typically call all but the Task.WhenAll(IEnumerable<Task>) and Task.WhenAll(Task[]) methods to retrieve the returned Task<TResult>.Result property, which does block the calling thread.

The following example calls the Task.WhenAll(IEnumerable<Task>) method to create a continuation task that reflects the results of its ten antecedent tasks. Each antecedent task squares an index value that ranges from one to ten. If the antecedents complete successfully (their Task.Status property is TaskStatus.RanToCompletion), the Task<TResult>.Result property of the continuation is an array of the Task<TResult>.Result values returned by each antecedent. The example adds them to compute the sum of squares for all numbers between one and ten.

using System.Collections.Generic;
using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      List<Task<int>> tasks = new List<Task<int>>();
      for (int ctr = 1; ctr <= 10; ctr++) {
         int baseValue = ctr;
         tasks.Add(Task.Factory.StartNew( (b) => { int i = (int) b;
                                                   return i * i; }, baseValue));
      }
      var continuation = Task.WhenAll(tasks);

      long sum = 0;
      for (int ctr = 0; ctr <= continuation.Result.Length - 1; ctr++) {
         Console.Write("{0} {1} ", continuation.Result[ctr],
                       ctr == continuation.Result.Length - 1 ? "=" : "+");
         sum += continuation.Result[ctr];
      }
      Console.WriteLine(sum);
   }
}
// The example displays the following output:
//    1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

When you create a single-task continuation, you can use a ContinueWith overload that takes a System.Threading.Tasks.TaskContinuationOptions enumeration value to specify the conditions under which the continuation starts. For example, you can specify that the continuation is to run only if the antecedent completes successfully, or only if it completes in a faulted state. If the condition is not true when the antecedent is ready to invoke the continuation, the continuation transitions directly to the TaskStatus.Canceled state and cannot be started after that.

A number of multi-task continuation methods, such as overloads of the TaskFactory.ContinueWhenAll method, also include a System.Threading.Tasks.TaskContinuationOptions parameter. Only a subset of all System.Threading.Tasks.TaskContinuationOptions enumeration members are valid, however. You can specify System.Threading.Tasks.TaskContinuationOptions values that have counterparts in the System.Threading.Tasks.TaskCreationOptions enumeration, such as TaskContinuationOptions.AttachedToParentTaskContinuationOptions.LongRunning, and TaskContinuationOptions.PreferFairness. If you specify any of the NotOn or OnlyOn options with a multi-task continuation, an ArgumentOutOfRangeException exception will be thrown at run time.

For more information on task continuation options, see the TaskContinuationOptions topic.

The Task.ContinueWith method passes a reference to the antecedent to the user delegate of the continuation as an argument. If the antecedent is a System.Threading.Tasks.Task<TResult> object, and the task ran until it was completed, then the continuation can access the Task<TResult>.Result property of the task.

The Task<TResult>.Result property blocks until the task has completed. However, if the task was canceled or faulted, attempting to access the Result property throws an AggregateException exception. You can avoid this problem by using the OnlyOnRanToCompletion option, as shown in the following example.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var t = Task.Run( () => { DateTime dat = DateTime.Now;
                                if (dat == DateTime.MinValue)
                                   throw new ArgumentException("The clock is not working.");
                                   
                                if (dat.Hour > 17)
                                   return "evening";
                                else if (dat.Hour > 12)
                                   return "afternoon";
                                else
                                   return "morning"; });
      var c = t.ContinueWith( (antecedent) => { Console.WriteLine("Good {0}!",
                                                                  antecedent.Result);
                                                Console.WriteLine("And how are you this fine {0}?",
                                                                  antecedent.Result); },
                              TaskContinuationOptions.OnlyOnRanToCompletion);
   }
}
// The example displays output like the following:
//       Good afternoon!
//       And how are you this fine afternoon?

If you want the continuation to run even if the antecedent did not run to successful completion, you must guard against the exception. One approach is to test the Task.Status property of the antecedent, and only attempt to access the Result property if the status is not Faulted or Canceled. You can also examine the Exception property of the antecedent. For more information, see Exception Handling. The following example modifies the previous example to access antecedent's Task<TResult>.Result property only if its status is TaskStatus.RanToCompletion.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var t = Task.Run( () => { DateTime dat = DateTime.Now;
                                if (dat == DateTime.MinValue)
                                   throw new ArgumentException("The clock is not working.");
                                   
                                if (dat.Hour > 17)
                                   return "evening";
                                else if (dat.Hour > 12)
                                   return "afternoon";
                                else
                                   return "morning"; });
      var c = t.ContinueWith( (antecedent) => { if (t.Status == TaskStatus.RanToCompletion) {
                                                   Console.WriteLine("Good {0}!",
                                                                     antecedent.Result);
                                                   Console.WriteLine("And how are you this fine {0}?",
                                                                  antecedent.Result);
                                                }
                                                else if (t.Status == TaskStatus.Faulted) {
                                                   Console.WriteLine(t.Exception.GetBaseException().Message);
                                                }} );
   }
}
// The example displays output like the following:
//       Good afternoon!
//       And how are you this fine afternoon?

The Task.Status property of a continuation is set to TaskStatus.Canceled in the following situations:

If a task and its continuation represent two parts of the same logical operation, you can pass the same cancellation token to both tasks, as shown in the following example. It consists of an antecedent that generates a list of integers that are divisible by 33, which it passes to the continuation. The continuation in turn displays the list. Both the antecedent and the continuation pause regularly for random intervals. In addition, a System.Threading.Timer object is used to execute the Elapsed method after a five-second timeout interval. This calls the CancellationTokenSource.Cancel method, which causes the currently executing task to call the CancellationToken.ThrowIfCancellationRequested method. Whether the CancellationTokenSource.Cancel method is called when the antecedent or its continuation is executing depends on the duration of the randomly generated pauses. If the antecedent is canceled, the continuation will not start. If the antecedent is not canceled, the token can still be used to cancel the continuation.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Random rnd = new Random();
      var cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;
      Timer timer = new Timer(Elapsed, cts, 5000, Timeout.Infinite);

      var t = Task.Run( () => { List<int> product33 = new List<int>();
                                for (int ctr = 1; ctr < Int16.MaxValue; ctr++) {
                                   if (token.IsCancellationRequested) {
                                      Console.WriteLine("\nCancellation requested in antecedent...\n");
                                      token.ThrowIfCancellationRequested();
                                   }
                                   if (ctr % 2000 == 0) {
                                      int delay = rnd.Next(16,501);
                                      Thread.Sleep(delay);
                                   }

                                   if (ctr % 33 == 0)
                                      product33.Add(ctr);
                                }
                                return product33.ToArray();
                              }, token);

      Task continuation = t.ContinueWith(antecedent => { Console.WriteLine("Multiples of 33:\n");
                                                         var arr = antecedent.Result;
                                                         for (int ctr = 0; ctr < arr.Length; ctr++)
                                                         {
                                                            if (token.IsCancellationRequested) {
                                                               Console.WriteLine("\nCancellation requested in continuation...\n");
                                                               token.ThrowIfCancellationRequested();
                                                            }

                                                            if (ctr % 100 == 0) {
                                                               int delay = rnd.Next(16,251);
                                                               Thread.Sleep(delay);
                                                            }
                                                            Console.Write("{0:N0}{1}", arr[ctr],
                                                                          ctr != arr.Length - 1 ? ", " : "");
                                                            if (Console.CursorLeft >= 74)
                                                               Console.WriteLine();
                                                         }
                                                         Console.WriteLine();
                                                       } , token);

      try {
          continuation.Wait();
      }
      catch (AggregateException e) {
         foreach (Exception ie in e.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name,
                              ie.Message);
      }
      finally {
         cts.Dispose();
      }

      Console.WriteLine("\nAntecedent Status: {0}", t.Status);
      Console.WriteLine("Continuation Status: {0}", continuation.Status);
  }

   private static void Elapsed(object state)
   {
      CancellationTokenSource cts = state as CancellationTokenSource;
      if (cts == null) return;

      cts.Cancel();
      Console.WriteLine("\nCancellation request issued...\n");
   }
}
// The example displays the following output:
//    Multiples of 33:
//
//    33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
//    561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
//    1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
//    1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
//    1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
//    2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
//    2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
//    2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
//    3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
//    3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
//    3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
//    4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
//    4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
//    5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
//    5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
//    5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
//    6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
//    6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
//    6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
//    7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
//    7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
//    7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
//    8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
//    8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
//    9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
//    9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
//    9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
//    10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
//    10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
//    10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
//    11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
//    11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
//    11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
//    12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
//    12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
//    12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
//    13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
//    Cancellation requested in continuation...
//
//
//    Cancellation request issued...
//
//    TaskCanceledException: A task was canceled.
//
//    Antecedent Status: RanToCompletion
//    Continuation Status: Canceled

You can also prevent a continuation from executing if its antecedent is canceled without supplying the continuation a cancellation token by specifying the TaskContinuationOptions.NotOnCanceled option when you create the continuation. The following is a simple example.

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

public class Example
{
   public static void Main()
   {
      var cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;
      cts.Cancel();

      var t = Task.FromCanceled(token);
      var continuation = t.ContinueWith( (antecedent) => {
                                            Console.WriteLine("The continuation is running.");
                                          } , TaskContinuationOptions.NotOnCanceled);
      try {
         t.Wait();
      }
      catch (AggregateException ae) {
         foreach (var ie in ae.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);

         Console.WriteLine();
      }
      finally {
         cts.Dispose();
      }

      Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status);
      Console.WriteLine("Task {0}: {1:G}", continuation.Id,
                        continuation.Status);
   }
}
// The example displays the following output:
//       TaskCanceledException: A task was canceled.
//
//       Task 1: Canceled
//       Task 2: Canceled

After a continuation goes into the Canceled state, it may affect continuations that follow, depending on the TaskContinuationOptions that were specified for those continuations.

Continuations that are disposed will not start.

A continuation does not run until the antecedent and all of its attached child tasks have completed. The continuation does not wait for detached child tasks to finish. The following two examples illustrate child tasks that are attached to and detached from an antecedent that creates a continuation. In the following example, the continuation runs only after all child tasks have completed, and running the example multiple times produces identical output each time. Note that the example launches the antecedent by calling the TaskFactory.StartNew method, since by default the Task.Run method creates a parent task whose default task creation option is TaskCreationOptions.DenyChildAttach.

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

public class Example
{
   public static void Main()
   {
      var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
                                                  Task.CurrentId);
                                             Console.WriteLine("Launching attached child tasks...");
                                             for (int ctr = 1; ctr <= 5; ctr++)  {
                                                int index = ctr;
                                                Task.Factory.StartNew( (value) => {
                                                                       Console.WriteLine("   Attached child task #{0} running",
                                                                                         value);
                                                                       Thread.Sleep(1000);
                                                                     }, index, TaskCreationOptions.AttachedToParent);
                                             }
                                             Console.WriteLine("Finished launching attached child tasks...");
                                           });
      var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task {0}",
                                                                             antecedent.Id);
                                                         });
      continuation.Wait();
   }
}
// The example displays the following output:
//       Running antecedent task 1...
//       Launching attached child tasks...
//       Finished launching attached child tasks...
//          Attached child task #5 running
//          Attached child task #1 running
//          Attached child task #2 running
//          Attached child task #3 running
//          Attached child task #4 running
//       Executing continuation of Task 1

If child tasks are detached from the antecedent, however, the continuation runs as soon as the antecedent has terminated, regardless of the state of the child tasks. As a result, multiple runs of the following example can produce variable output that depends on how the task scheduler handled each child task.

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

public class Example
{
   public static void Main()
   {
      var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
                                                  Task.CurrentId);
                                             Console.WriteLine("Launching attached child tasks...");
                                             for (int ctr = 1; ctr <= 5; ctr++)  {
                                                int index = ctr;
                                                Task.Factory.StartNew( (value) => {
                                                                       Console.WriteLine("   Attached child task #{0} running",
                                                                                         value);
                                                                       Thread.Sleep(1000);
                                                                     }, index);
                                             }
                                             Console.WriteLine("Finished launching detached child tasks...");
                                           }, TaskCreationOptions.DenyChildAttach);
      var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task {0}",
                                                                             antecedent.Id);
                                                         });
      continuation.Wait();
   }
}
// The example displays output like the following:
//       Running antecedent task 1...
//       Launching attached child tasks...
//       Finished launching detached child tasks...
//          Attached child task #1 running
//          Attached child task #2 running
//          Attached child task #5 running
//          Attached child task #3 running
//       Executing continuation of Task 1
//          Attached child task #4 running

The final status of the antecedent task depends on the final status of any attached child tasks. The status of detached child tasks does not affect the parent. For more information, see Attached and Detached Child Tasks.

You can associate arbitrary state with a task continuation. The ContinueWith method provides overloaded versions that each take an Object value that represents the state of the continuation. You can later access this state object by using the Task.AsyncState property. This state object is null if you do not provide a value.

Continuation state is useful when you convert existing code that uses the Asynchronous Programming Model (APM) to use the TPL. In the APM, you typically provide object state in the BeginMethod** method and later access that state by using the IAsyncResult.AsyncState property. By using the ContinueWith method, you can preserve this state when you convert code that uses the APM to use the TPL.

Continuation state can also be useful when you work with Task objects in the Visual Studio debugger. For example, in the Parallel Tasks window, the Task column displays the string representation of the state object for each task. For more information about the Parallel Tasks window, see Using the Tasks Window.

The following example shows how to use continuation state. It creates a chain of continuation tasks. Each task provides the current time, a DateTime object, for the state parameter of the ContinueWith method. Each DateTime object represents the time at which the continuation task is created. Each task produces as its result a second DateTime object that represents the time at which the task finishes. After all tasks finish, this example displays the creation time and the time at which each continuation task finishes.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

// Demonstrates how to associate state with task continuations.
class ContinuationState
{
   // Simluates a lengthy operation and returns the time at which
   // the operation completed.
   public static DateTime DoWork()
   {
      // Simulate work by suspending the current thread 
      // for two seconds.
      Thread.Sleep(2000);

      // Return the current time.
      return DateTime.Now;
   }

   static void Main(string[] args)
   {
      // Start a root task that performs work.
      Task<DateTime> t = Task<DateTime>.Run(delegate { return DoWork(); });

      // Create a chain of continuation tasks, where each task is 
      // followed by another task that performs work.
      List<Task<DateTime>> continuations = new List<Task<DateTime>>();
      for (int i = 0; i < 5; i++)
      {
         // Provide the current time as the state of the continuation.
         t = t.ContinueWith(delegate { return DoWork(); }, DateTime.Now);
         continuations.Add(t);
      }

      // Wait for the last task in the chain to complete.
      t.Wait();

      // Print the creation time of each continuation (the state object)
      // and the completion time (the result of that task) to the console.
      foreach (var continuation in continuations)
      {
         DateTime start = (DateTime)continuation.AsyncState;
         DateTime end = continuation.Result;

         Console.WriteLine("Task was created at {0} and finished at {1}.",
            start.TimeOfDay, end.TimeOfDay);
      }
   }
}

/* Sample output:
Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
*/

An antecedent-continuation relationship is not a parent-child relationship. Exceptions thrown by continuations are not propagated to the antecedent. Therefore, handle exceptions thrown by continuations as you would handle them in any other task, as follows:

  • You can use the WaitWaitAll, or WaitAny method, or its generic counterpart, to wait on the continuation. You can wait for an antecedent and its continuations in the same try statement, as shown in the following example.

    using System;
    using System.Threading.Tasks;
    
    public class Example
    {
       public static void Main()
       {
          var task1 = Task<int>.Run( () => { Console.WriteLine("Executing task {0}",
                                                               Task.CurrentId);
                                             return 54; });
          var continuation = task1.ContinueWith( (antecedent) =>
                                                 { Console.WriteLine("Executing continuation task {0}",
                                                                     Task.CurrentId);
                                                   Console.WriteLine("Value from antecedent: {0}",
                                                                     antecedent.Result);
                                                   throw new InvalidOperationException();
                                                } );
    
          try {
             task1.Wait();
             continuation.Wait();
          }
          catch (AggregateException ae) {
              foreach (var ex in ae.InnerExceptions)
                  Console.WriteLine(ex.Message);
          }
       }
    }
    // The example displays the following output:
    //       Executing task 1
    //       Executing continuation task 2
    //       Value from antecedent: 54
    //       Operation is not valid due to the current state of the object.
    

  • You can use a second continuation to observe the Exception property of the first continuation. In the following example, a task attempts to read from a non-existent file. The continuation then displays information about the exception in the antecedent task.

    using System;
    using System.IO;
    using System.Threading.Tasks;
    
    public class Example
    {
       public static void Main()
       {
          var t = Task.Run( () => { string s = File.ReadAllText(@"C:\NonexistentFile.txt");
                                    return s;
                                  });
    
          var c = t.ContinueWith( (antecedent) =>
                                  { // Get the antecedent's exception information.
                                    foreach (var ex in antecedent.Exception.InnerExceptions) {
                                       if (ex is FileNotFoundException)
                                          Console.WriteLine(ex.Message);
                                    }
                                  }, TaskContinuationOptions.OnlyOnFaulted);
    
          c.Wait();
       }
    }
    // The example displays the following output:
    //        Could not find file 'C:\NonexistentFile.txt'.
    

    Because it was run with the TaskContinuationOptions.OnlyOnFaulted option, the continuation executes only if an exception occurs in the antecedent, and therefore it can assume that the antecedent's Exception property is not null. If the continuation executes whether or not an exception is thrown in the antecedent, it would have to check whether the antecedent's Exception property is not null before attempting to handle the exception, as the following code fragment shows.

                                    // Determine whether an exception occurred.
                                    if (antecedent.Exception != null) {
                                       foreach (var ex in antecedent.Exception.InnerExceptions) {
                                          if (ex is FileNotFoundException)
                                             Console.WriteLine(ex.Message);
                                       }
                                    }
    

    For more information, see Exception Handling and NIB: How to: Handle Exceptions Thrown by Tasks.

  • If the continuation is an attached child task that was created by using the TaskContinuationOptions.AttachedToParent option, its exceptions will be propagated by the parent back to the calling thread, as is the case in any other attached child. For more information, see Attached and Detached Child Tasks.






Attached and Detached Child Tasks


A child task (or nested task) is a System.Threading.Tasks.Task instance that is created in the user delegate of another task, which is known as the parent task. A child task can be either detached or attached. A detached child task is a task that executes independently of its parent. An attached child task is a nested task that is created with the TaskCreationOptions.AttachedToParent option whose parent does not explicitly or by default prohibit it from being attached. A task may create any number of attached and detached child tasks, limited only by system resources.

The following table lists the basic differences between the two kinds of child tasks.

CategoryDetached child tasksAttached child tasks
Parent waits for child tasks to complete.NoYes
Parent propagates exceptions thrown by child tasks.NoYes
Status of parent depends on status of child.NoYes

In most scenarios, we recommend that you use detached child tasks, because their relationships with other tasks are less complex. That is why tasks created inside parent tasks are detached by default, and you must explicitly specify the TaskCreationOptions.AttachedToParent option to create an attached child task.

Although a child task is created by a parent task, by default it is independent of the parent task. In the following example, a parent task creates one simple child task. If you run the example code multiple times, you may notice that the output from the example differs from that shown, and also that the output may change each time you run the code. This occurs because the parent task and child tasks execute independently of each other; the child is a detached task. The example waits only for the parent task to complete, and the child task may not execute or complete before the console app terminates.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
         Console.WriteLine("Outer task executing.");

         var child = Task.Factory.StartNew(() => {
            Console.WriteLine("Nested task starting.");
            Thread.SpinWait(500000);
            Console.WriteLine("Nested task completing.");
         });
      });

      parent.Wait();
      Console.WriteLine("Outer has completed.");
   }
}
// The example produces output like the following:
//        Outer task executing.
//        Nested task starting.
//        Outer has completed.
//        Nested task completing.

If the child task is represented by a Task<TResult> object rather than a Task object, you can ensure that the parent task will wait for the child to complete by accessing the Task<TResult>.Result property of the child even if it is a detached child task. The Result property blocks until its task completes, as the following example shows.

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

class Example
{
   static void Main()
   {
      var outer = Task<int>.Factory.StartNew(() => {
            Console.WriteLine("Outer task executing.");

            var nested = Task<int>.Factory.StartNew(() => {
                  Console.WriteLine("Nested task starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Nested task completing.");
                  return 42;
            });

            // Parent will wait for this detached child.
            return nested.Result;
      });

      Console.WriteLine("Outer has returned {0}.", outer.Result);
   }
}
// The example displays the following output:
//       Outer task executing.
//       Nested task starting.
//       Nested task completing.
//       Outer has returned 42.

Unlike detached child tasks, attached child tasks are closely synchronized with the parent. You can change the detached child task in the previous example to an attached child task by using the TaskCreationOptions.AttachedToParent option in the task creation statement, as shown in the following example. In this code, the attached child task completes before its parent. As a result, the output from the example is the same each time you run the code.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Attached child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Attached child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays the following output:
//       Parent task executing.
//       Attached child starting.
//       Attached child completing.
//       Parent has completed.

You can use attached child tasks to create tightly synchronized graphs of asynchronous operations.

However, a child task can attach to its parent only if its parent does not prohibit attached child tasks. Parent tasks can explicitly prevent child tasks from attaching to them by specifying the TaskCreationOptions.DenyChildAttach option in the parent task's class constructor or the TaskFactory.StartNew method. Parent tasks implicitly prevent child tasks from attaching to them if they are created by calling the Task.Run method. The following example illustrates this. It is identical to the previous example, except that the parent task is created by calling the Task.Run(Action) method rather than the TaskFactory.StartNew(Action) method. Because the child task is not able to attach to its parent, the output from the example is unpredictable. Because the default task creation options for the Task.Run overloads include TaskCreationOptions.DenyChildAttach, this example is functionally equivalent to the first example in the "Detached child tasks" section.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Run(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays output like the following:
//       Parent task executing
//       Parent has completed.
//       Attached child starting.

If a detached child task throws an exception, that exception must be observed or handled directly in the parent task just as with any non-nested task. If an attached child task throws an exception, the exception is automatically propagated to the parent task and back to the thread that waits or tries to access the task's Task<TResult>.Result property. Therefore, by using attached child tasks, you can handle all exceptions at just one point in the call to Task.Wait on the calling thread. For more information, see Exception Handling.

Task cancellation is cooperative. That is, to be cancelable, every attached or detached child task must monitor the status of the cancellation token. If you want to cancel a parent and all its children by using one cancellation request, you pass the same token as an argument to all tasks and provide in each task the logic to respond to the request in each task. For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

When the parent cancels

If a parent cancels itself before its child task is started, the child never starts. If a parent cancels itself after its child task has already started, the child runs to completion unless it has its own cancellation logic. For more information, see Task Cancellation.

When a detached child task cancels

If a detached child task cancels itself by using the same token that was passed to the parent, and the parent does not wait for the child task, no exception is propagated, because the exception is treated as benign cooperation cancellation. This behavior is the same as that of any top-level task.

When an attached child task cancels

When an attached child task cancels itself by using the same token that was passed to its parent task, a TaskCanceledException is propagated to the joining thread inside an AggregateException. You must wait for the parent task so that you can handle all benign exceptions in addition to all faulting exceptions that are propagated up through a graph of attached child tasks.

For more information, see Exception Handling.

An unhandled exception that is thrown by a child task is propagated to the parent task. You can use this behavior to observe all child task exceptions from one root task instead of traversing a tree of tasks. However, exception propagation can be problematic when a parent task does not expect attachment from other code. For example, consider an app that calls a third-party library component from a Task object. If the third-party library component also creates a Task object and specifies TaskCreationOptions.AttachedToParent to attach it to the parent task, any unhandled exceptions that occur in the child task propagate to the parent. This could lead to unexpected behavior in the main app.

To prevent a child task from attaching to its parent task, specify the TaskCreationOptions.DenyChildAttach option when you create the parent Task or Task<TResult> object. When a task tries to attach to its parent and the parent specifies the TaskCreationOptions.DenyChildAttach option, the child task will not be able to attach to a parent and will execute just as if the TaskCreationOptions.AttachedToParent option was not specified.

You might also want to prevent a child task from attaching to its parent when the child task does not finish in a timely manner. Because a parent task does not finish until all child tasks finish, a long-running child task can cause the overall app to perform poorly. For an example that shows how to improve app performance by preventing a task from attaching to its parent task, see How to: Prevent a Child Task from Attaching to its Parent.






Task Cancellation


The System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> classes support cancellation through the use of cancellation tokens in the .NET Framework. For more information, see Cancellation in Managed Threads. In the Task classes, cancellation involves cooperation between the user delegate, which represents a cancelable operation and the code that requested the cancellation. A successful cancellation involves the requesting code calling the CancellationTokenSource.Cancel method, and the user delegate terminating the operation in a timely manner. You can terminate the operation by using one of these options:

  • By simply returning from the delegate. In many scenarios this is sufficient; however, a task instance that is canceled in this way transitions to the TaskStatus.RanToCompletion state, not to the TaskStatus.Canceled state.

  • By throwing a OperationCanceledException and passing it the token on which cancellation was requested. The preferred way to do this is to use the ThrowIfCancellationRequested method. A task that is canceled in this way transitions to the Canceled state, which the calling code can use to verify that the task responded to its cancellation request.

The following example shows the basic pattern for task cancellation that throws the exception. Note that the token is passed to the user delegate and to the task instance itself.

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static void Main()
    {
        var tokenSource2 = new CancellationTokenSource();
        CancellationToken ct = tokenSource2.Token;

        var task = Task.Factory.StartNew(() =>
        {

            // Were we already canceled?
            ct.ThrowIfCancellationRequested();

            bool moreToDo = true;
            while (moreToDo)
            {
                // Poll on this property if you have to do
                // other cleanup before throwing.
                if (ct.IsCancellationRequested)
                {
                    // Clean up here, then...
                    ct.ThrowIfCancellationRequested();
                }

            }
        }, tokenSource2.Token); // Pass same token to StartNew.

        tokenSource2.Cancel();

        // Just continue on this thread, or Wait/WaitAll with try-catch:
        try
        {
            task.Wait();
        }
        catch (AggregateException e)
        {
            foreach (var v in e.InnerExceptions)
                Console.WriteLine(e.Message + " " + v.Message);
        }
        finally
        {
            tokenSource2.Dispose();
        }

        Console.ReadKey();
    }
}

For a more complete example, see How to: Cancel a Task and Its Children.

When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state. If you do not use a Wait or WaitAll method to wait for the task, then the task just sets its status to Canceled.

If you are waiting on a Task that transitions to the Canceled state, a System.Threading.Tasks.TaskCanceledException exception (wrapped in an AggregateException exception) is thrown. Note that this exception indicates successful cancellation instead of a faulty situation. Therefore, the task's Exception property returns null.

If the token's IsCancellationRequested property returns false or if the exception's token does not match the Task's token, the OperationCanceledException is treated like a normal exception, causing the Task to transition to the Faulted state. Also note that the presence of other exceptions will also cause the Task to transition to the Faulted state. You can get the status of the completed task in the Status property.

It is possible that a task may continue to process some items after cancellation is requested.






Exception Handling (Task Parallel Library)


Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the calling thread, except in certain scenarios that are described later in this topic. Exceptions are propagated when you use one of the static or instance Task.Wait or Task<TResult>.Wait methods, and you handle them by enclosing the call in a try/catch statement. If a task is the parent of attached child tasks, or if you are waiting on multiple tasks, multiple exceptions could be thrown.

To propagate all the exceptions back to the calling thread, the Task infrastructure wraps them in an AggregateException instance. The AggregateException exception has an InnerExceptions property that can be enumerated to examine all the original exceptions that were thrown, and handle (or not handle) each one individually. You can also handle the original exceptions by using the AggregateException.Handle method.

Even if only one exception is thrown, it is still wrapped in an AggregateException exception, as the following example shows.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try
      {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
          foreach (var e in ae.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw;
              }
          }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays the following output:
//        This exception is expected!

You could avoid an unhandled exception by just catching the AggregateException and not observing any of the inner exceptions. However, we recommend that you do not do this because it is analogous to catching the base Exception type in non-parallel scenarios. To catch an exception without taking specific actions to recover from it can leave your program in an indeterminate state.

If you do not want to call the Task.Wait or Task<TResult>.Wait method to wait for a task's completion, you can also retrieve the AggregateException exception from the task's Exception property, as the following example shows. For more information, see the Observing Exceptions By Using the Task.Exception Property section in this topic.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      while(! task1.IsCompleted) {}

      if (task1.Status == TaskStatus.Faulted) {
          foreach (var e in task1.Exception.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw e;
              }
          }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays the following output:
//        This exception is expected!

If you do not wait on a task that propagates an exception, or access its Exception property, the exception is escalated according to the .NET exception policy when the task is garbage-collected.

When exceptions are allowed to bubble up back to the joining thread, it is possible that a task may continue to process some items after the exception is raised.

System_CAPS_ICON_note.jpg Note

When "Just My Code" is enabled, Visual Studio in some cases will break on the line that throws the exception and display an error message that says "exception not handled by user code." This error is benign. You can press F5 to continue and see the exception-handling behavior that is demonstrated in these examples. To prevent Visual Studio from breaking on the first error, just uncheck the Enable Just My Code checkbox under Tools, Options, Debugging, General.

If a task has an attached child task that throws an exception, that exception is wrapped in an AggregateException before it is propagated to the parent task, which wraps that exception in its own AggregateException before it propagates it back to the calling thread. In such cases, the InnerExceptions property of the AggregateException exception that is caught at the Task.Wait or Task<TResult>.Wait or WaitAny or WaitAll method contains one or more AggregateException instances, not the original exceptions that caused the fault. To avoid having to iterate over nested AggregateException exceptions, you can use the Flatten method to remove all the nested AggregateException exceptions, so that the AggregateException.InnerExceptions property contains the original exceptions. In the following example, nested AggregateException instances are flattened and handled in just one loop.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Factory.StartNew(() => {
                     var child1 = Task.Factory.StartNew(() => {
                        var child2 = Task.Factory.StartNew(() => {
                            // This exception is nested inside three AggregateExceptions.
                            throw new CustomException("Attached child2 faulted.");
                        }, TaskCreationOptions.AttachedToParent);

                        // This exception is nested inside two AggregateExceptions.
                        throw new CustomException("Attached child1 faulted.");
                     }, TaskCreationOptions.AttachedToParent);
      });

      try {
         task1.Wait();
      }
      catch (AggregateException ae) {
         foreach (var e in ae.Flatten().InnerExceptions) {
            if (e is CustomException) {
               Console.WriteLine(e.Message);
            }
            else {
               throw;
            }
         }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays the following output:
//    Attached child1 faulted.
//    Attached child2 faulted.

You can also use the AggregateException.Flatten method to rethrow the inner exceptions from multiple AggregateException instances thrown by multiple tasks in a single AggregateException instance, as the following example shows.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
public class Example
{
   public static void Main()
   {
       try {
          ExecuteTasks();
       }
       catch (AggregateException ae) {
          foreach (var e in ae.InnerExceptions)
             Console.WriteLine("{0}:\n   {1}", e.GetType().Name, e.Message);

       }
   }

   static void ExecuteTasks()
   {
        // Assume this is a user-entered String.
        String path = @"C:\";
        List<Task> tasks = new List<Task>();

        tasks.Add(Task.Run(() => {
                             // This should throw an UnauthorizedAccessEXception.
                              return Directory.GetFiles(path, "*.txt",
                                                        SearchOption.AllDirectories);
                           } ));

        tasks.Add(Task.Run(() => {
                              if (path == @"C:\")
                                 throw new ArgumentException("The system root is not a valid path.");
                              return new String[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
                           } ));

        tasks.Add(Task.Run( () => {
                               throw new NotImplementedException("This operation has not been implemented.");
                           } ));

        try {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException ae) {
            throw ae.Flatten();
        }
    }
}
// The example displays the following output:
//       UnauthorizedAccessException:
//          Access to the path 'C:\Documents and Settings' is denied.
//       ArgumentException:
//          The system root is not a valid path.
//       NotImplementedException:
//          This operation has not been implemented.

By default, child tasks are created as detached. Exceptions thrown from detached tasks must be handled or rethrown in the immediate parent task; they are not propagated back to the calling thread in the same way as attached child tasks propagated back. The topmost parent can manually rethrow an exception from a detached child to cause it to be wrapped in an AggregateException and propagated back to the calling thread.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run(() => {
                       var nested1 = Task.Run(() => {
                                          throw new CustomException("Detached child task faulted.");
                                     });

          // Here the exception will be escalated back to the calling thread.
          // We could use try/catch here to prevent that.
          nested1.Wait();
      });

      try {
         task1.Wait();
      }
      catch (AggregateException ae) {
         foreach (var e in ae.Flatten().InnerExceptions) {
            if (e is CustomException) {
               Console.WriteLine(e.Message);
            }
         }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays the following output:
//    Detached child task faulted.

Even if you use a continuation to observe an exception in a child task, the exception still must be observed by the parent task.

When user code in a task responds to a cancellation request, the correct procedure is to throw an OperationCanceledException passing in the cancellation token on which the request was communicated. Before it attempts to propagate the exception, the task instance compares the token in the exception to the one that was passed to it when it was created. If they are the same, the task propagates a TaskCanceledException wrapped in the AggregateException, and it can be seen when the inner exceptions are examined. However, if the calling thread is not waiting on the task, this specific exception will not be propagated. For more information, see Task Cancellation.

            var tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;

            var task1 = Task.Factory.StartNew(() =>
            {
                CancellationToken ct = token;
                while (someCondition)
                {
                    // Do some work...
                    Thread.SpinWait(50000);
                    ct.ThrowIfCancellationRequested();
                }
            },
            token);

            // No waiting required.
            tokenSource.Dispose();

You can use the AggregateException.Handle method to filter out exceptions that you can treat as "handled" without using any further logic. In the user delegate that is supplied to the AggregateException.Handle(Func<Exception, Boolean>) method, you can examine the exception type, its Message property, or any other information about it that will let you determine whether it is benign. Any exceptions for which the delegate returns false are rethrown in a new AggregateException instance immediately after the AggregateException.Handle method returns.

The following example is functionally equivalent to the first example in this topic, which examines each exception in the AggregateException.InnerExceptions collection. Instead, this exception handler calls the AggregateException.Handle method object for each exception, and only rethrows exceptions that are not CustomException instances.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
         // Call the Handle method to handle the custom exception,
         // otherwise rethrow the exception.
         ae.Handle(ex => { if (ex is CustomException)
                             Console.WriteLine(ex.Message);
                          return ex is CustomException;
                        });
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays the following output:
//        This exception is expected!

The following is a more complete example that uses the AggregateException.Handle method to provide special handling for an UnauthorizedAccessException exception when enumerating files.

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

public class Example
{
    public static void Main()
    {
        // This should throw an UnauthorizedAccessException.
       try {
           var files = GetAllFiles(@"C:\");
           if (files != null)
              foreach (var file in files)
                 Console.WriteLine(file);
        }
        catch (AggregateException ae) {
           foreach (var ex in ae.InnerExceptions)
               Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
        }
        Console.WriteLine();

        // This should throw an ArgumentException.
        try {
           foreach (var s in GetAllFiles(""))
              Console.WriteLine(s);
        }
        catch (AggregateException ae) {
           foreach (var ex in ae.InnerExceptions)
               Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
        }
    }

    static string[] GetAllFiles(string path)
    {
       var task1 = Task.Run( () => Directory.GetFiles(path, "*.txt",
                                                      SearchOption.AllDirectories));

       try {
          return task1.Result;
       }
       catch (AggregateException ae) {
          ae.Handle( x => { // Handle an UnauthorizedAccessException
                            if (x is UnauthorizedAccessException) {
                                Console.WriteLine("You do not have permission to access all folders in this path.");
                                Console.WriteLine("See your network administrator or try another path.");
                            }
                            return x is UnauthorizedAccessException;
                          });
          return Array.Empty<String>();
       }
   }
}
// The example displays the following output:
//       You do not have permission to access all folders in this path.
//       See your network administrator or try another path.
//
//       ArgumentException: The path is not of a legal form.

If a task completes in the TaskStatus.Faulted state, its Exception property can be examined to discover which specific exception caused the fault. A good way to observe the Exception property is to use a continuation that runs only if the antecedent task faults, as shown in the following example.

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

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run(() =>
                           { throw new CustomException("task1 faulted.");
      }).ContinueWith( t => { Console.WriteLine("{0}: {1}",
                                                t.Exception.InnerException.GetType().Name,
                                                t.Exception.InnerException.Message);
                            }, TaskContinuationOptions.OnlyOnFaulted);
      Thread.Sleep(500);
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// The example displays output like the following:
//        CustomException: task1 faulted.

In a real application, the continuation delegate could log detailed information about the exception and possibly spawn new tasks to recover from the exception.

In some scenarios, such as when hosting untrusted plug-ins, benign exceptions might be common, and it might be too difficult to manually observe them all. In these cases, you can handle the TaskScheduler.UnobservedTaskException event. The System.Threading.Tasks.UnobservedTaskExceptionEventArgs instance that is passed to your handler can be used to prevent the unobserved exception from being propagated back to the joining thread.






How to: Use Parallel.Invoke to Execute Parallel Operations


This example shows how to parallelize operations by using Invoke in the Task Parallel Library. Three operations are performed on a shared data source. Because none of the operations modifies the source, they can be executed in parallel in a straightforward manner.

System_CAPS_ICON_note.jpg Note

This documentation uses lambda expressions to define delegates in TPL. If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda Expressions in PLINQ and TPL.

namespace ParallelTasks
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Net;

    class ParallelInvoke
    {
        static void Main()
        {
            // Retrieve Darwin's "Origin of the Species" from Gutenberg.org.
            string[] words = CreateWordArray(@"http://www.gutenberg.org/files/2009/2009.txt");

            #region ParallelTasks
            // Perform three tasks in parallel on the source array
            Parallel.Invoke(() =>
                             {
                                 Console.WriteLine("Begin first task...");
                                 GetLongestWord(words);
                             },  // close first Action

                             () =>
                             {
                                 Console.WriteLine("Begin second task...");
                                 GetMostCommonWords(words);
                             }, //close second Action

                             () =>
                             {
                                 Console.WriteLine("Begin third task...");
                                 GetCountForWord(words, "species");
                             } //close third Action
                         ); //close parallel.invoke

            Console.WriteLine("Returned from Parallel.Invoke");
            #endregion

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }

        #region HelperMethods
        private static void GetCountForWord(string[] words, string term)
        {
            var findWord = from word in words
                           where word.ToUpper().Contains(term.ToUpper())
                           select word;

            Console.WriteLine(@"Task 3 -- The word ""{0}"" occurs {1} times.",
                term, findWord.Count());
        }

        private static void GetMostCommonWords(string[] words)
        {
            var frequencyOrder = from word in words
                                 where word.Length > 6
                                 group word by word into g
                                 orderby g.Count() descending
                                 select g.Key;

            var commonWords = frequencyOrder.Take(10);

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Task 2 -- The most common words are:");
            foreach (var v in commonWords)
            {
                sb.AppendLine("  " + v);
            }
            Console.WriteLine(sb.ToString());
        }

        private static string GetLongestWord(string[] words)
        {
            var longestWord = (from w in words
                               orderby w.Length descending
                               select w).First();

            Console.WriteLine("Task 1 -- The longest word is {0}", longestWord);
            return longestWord;
        }


        // An http request performed synchronously for simplicity.
        static string[] CreateWordArray(string uri)
        {
            Console.WriteLine("Retrieving from {0}", uri);

            // Download a web page the easy way.
            string s = new WebClient().DownloadString(uri);

            // Separate string into an array of words, removing some common punctuation.
            return s.Split(
                new char[] { ' ', '\u000A', ',', '.', ';', ':', '-', '_', '/' },
                StringSplitOptions.RemoveEmptyEntries);
        }
        #endregion
    }

    /* Output (May vary on each execution):
        Retrieving from http://www.gutenberg.org/dirs/etext99/otoos610.txt
        Response stream received.
        Begin first task...
        Begin second task...
        Task 2 -- The most common words are:
          species
          selection
          varieties
          natural
          animals
          between
          different
          distinct
          several
          conditions

        Begin third task...
        Task 1 -- The longest word is characteristically
        Task 3 -- The word "species" occurs 1927 times.
        Returned from Parallel.Invoke
        Press any key to exit  
     */
}

Note that with Invoke, you simply express which actions you want to run concurrently, and the runtime handles all thread scheduling details, including scaling automatically to the number of cores on the host computer.

This example parallelizes the operations, not the data. As an alternate approach, you can parallelize the LINQ queries by using PLINQ and run the queries sequentially. Alternatively, you could parallelize the data by using PLINQ. Another option is to parallelize both the queries and the tasks. Although the resulting overhead might degrade performance on host computers with relatively few processors, it would scale much better on computers with many processors.






How to: Return a Value from a Task


This example shows how to use the System.Threading.Tasks.Task<TResult> type to return a value from the Result property. It requires that the C:\Users\Public\Pictures\Sample Pictures\ directory exists, and that it contains files.

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

class Program
{
    static void Main()
    {
        // Return a value type with a lambda expression
        Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
        int i = task1.Result;

        // Return a named reference type with a multi-line statement lambda.
        Task<Test> task2 = Task<Test>.Factory.StartNew(() =>
        {
            string s = ".NET";
            double d = 4.0;
            return new Test { Name = s, Number = d };
        });
        Test test = task2.Result;

        // Return an array produced by a PLINQ query
        Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>
        {
            string path = @"C:\Users\Public\Pictures\Sample Pictures\";
            string[] files = System.IO.Directory.GetFiles(path);

            var result = (from file in files.AsParallel()
                          let info = new System.IO.FileInfo(file)
                          where info.Extension == ".jpg"
                          select file).ToArray();

            return result;
        });

        foreach (var name in task3.Result)
            Console.WriteLine(name);

    }
    class Test
    {
        public string Name { get; set; }
        public double Number { get; set; }

    }
}

The Result property blocks the calling thread until the task finishes.

To see how to pass the result of one System.Threading.Tasks.Task<TResult> to a continuation task, see Chaining Tasks by Using Continuation Tasks.






How to: Cancel a Task and Its Children


These examples show how to perform the following tasks:

  1. Create and start a cancelable task.

  2. Pass a cancellation token to your user delegate and optionally to the task instance.

  3. Notice and respond to the cancellation request in your user delegate.

  4. Optionally notice on the calling thread that the task was canceled.

The calling thread does not forcibly end the task; it only signals that cancellation is requested. If the task is already running, it is up to the user delegate to notice the request and respond appropriately. If cancellation is requested before the task runs, then the user delegate is never executed and the task object transitions into the Canceled state.

This example shows how to terminate a Task and its children in response to a cancellation request. It also shows that when a user delegate terminates by throwing a TaskCanceledException, the calling thread can optionally use the Wait method or WaitAll method to wait for the tasks to finish. In this case, you must use a try/catch block to handle the exceptions on the calling thread.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var tokenSource = new CancellationTokenSource();
      var token = tokenSource.Token;

      // Store references to the tasks so that we can wait on them and  
      // observe their status after cancellation. 
      Task t;
      var tasks = new ConcurrentBag<Task>();

      Console.WriteLine("Press any key to begin tasks...");
      Console.ReadKey(true);
      Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
      Console.WriteLine();
        
      // Request cancellation of a single task when the token source is canceled. 
      // Pass the token to the user delegate, and also to the task so it can  
      // handle the exception correctly.
      t = Task.Factory.StartNew( () => DoSomeWork(1, token), token);
      Console.WriteLine("Task {0} executing", t.Id);
      tasks.Add(t);

      // Request cancellation of a task and its children. Note the token is passed 
      // to (1) the user delegate and (2) as the second argument to StartNew, so  
      // that the task instance can correctly handle the OperationCanceledException.
      t = Task.Factory.StartNew( () => {
                                     // Create some cancelable child tasks.  
                                     Task tc;
                                     for (int i = 3; i <= 10; i++) {
                                        // For each child task, pass the same token 
                                        // to each user delegate and to StartNew.
                                        tc = Task.Factory.StartNew( iteration => DoSomeWork((int)iteration, token), i, token);
                                        Console.WriteLine("Task {0} executing", tc.Id);
                                        tasks.Add(tc);
                                        // Pass the same token again to do work on the parent task.  
                                        // All will be signaled by the call to tokenSource.Cancel below.
                                        DoSomeWork(2, token);
                                     } 
                                  }, 
                                  token);
                                           
        Console.WriteLine("Task {0} executing", t.Id);
        tasks.Add(t);

        // Request cancellation from the UI thread. 
        char ch = Console.ReadKey().KeyChar;
        if (ch == 'c'|| ch == 'C' ) {
            tokenSource.Cancel();
            Console.WriteLine("\nTask cancellation requested.");

            // Optional: Observe the change in the Status property on the task. 
            // It is not necessary to wait on tasks that have canceled. However, 
            // if you do wait, you must enclose the call in a try-catch block to 
            // catch the TaskCanceledExceptions that are thrown. If you do  
            // not wait, no exception is thrown if the token that was passed to the  
            // StartNew method is the same token that requested the cancellation. 
        } 

        try {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException e) {
           Console.WriteLine("\nAggregateException thrown with the following inner exceptions:");
           // Display information about each exception. 
           foreach (var v in e.InnerExceptions) {
              if (v is TaskCanceledException)
                 Console.WriteLine("   TaskCanceledException: Task {0}", 
                                   ((TaskCanceledException) v).Task.Id);
              else
                 Console.WriteLine("   Exception: {0}", v.GetType().Name);
           } 
           Console.WriteLine();
        }
        finally
        {
           tokenSource.Dispose();
        }

        // Display status of all tasks. 
        foreach (var task in tasks)
            Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
   }

   static void DoSomeWork(int taskNum, CancellationToken ct)
   {
      // Was cancellation already requested? 
      if (ct.IsCancellationRequested == true) {
         Console.WriteLine("Task {0} was cancelled before it got started.",
                           taskNum);
         ct.ThrowIfCancellationRequested();
      } 

      int maxIterations = 100;

      // NOTE!!! A "TaskCanceledException was unhandled 
      // by user code" error will be raised here if "Just My Code" 
      // is enabled on your computer. On Express editions JMC is 
      // enabled and cannot be disabled. The exception is benign. 
      // Just press F5 to continue executing your code. 
      for (int i = 0; i <= maxIterations; i++) {
         // Do a bit of work. Not too much. 
         var sw = new SpinWait();
         for (int j = 0; j <= 100; j++)
            sw.SpinOnce();

         if (ct.IsCancellationRequested) {
            Console.WriteLine("Task {0} cancelled", taskNum);
            ct.ThrowIfCancellationRequested();
         } 
      } 
   } 
}  
// The example displays output like the following:
//       Press any key to begin tasks...
//    To terminate the example, press 'c' to cancel and exit...
//    
//    Task 1 executing
//    Task 2 executing
//    Task 3 executing
//    Task 4 executing
//    Task 5 executing
//    Task 6 executing
//    Task 7 executing
//    Task 8 executing
//    c
//    Task cancellation requested.
//    Task 2 cancelled
//    Task 7 cancelled
//    
//    AggregateException thrown with the following inner exceptions:
//       TaskCanceledException: Task 2
//       TaskCanceledException: Task 8
//       TaskCanceledException: Task 7
//    
//    Task 2 status is now Canceled
//    Task 1 status is now RanToCompletion
//    Task 8 status is now Canceled
//    Task 7 status is now Canceled
//    Task 6 status is now RanToCompletion
//    Task 5 status is now RanToCompletion
//    Task 4 status is now RanToCompletion
//    Task 3 status is now RanToCompletion

The System.Threading.Tasks.Task class is fully integrated with the cancellation model that is based on the System.Threading.CancellationTokenSource and System.Threading.CancellationToken types. For more information, see Cancellation in Managed Threads and Task Cancellation.






How to: Create Pre-Computed Tasks


This document describes how to use the Task.FromResult<TResult> method to retrieve the results of asynchronous download operations that are held in a cache. The FromResult<TResult> method returns a finished Task<TResult> object that holds the provided value as its Result property. This method is useful when you perform an asynchronous operation that returns a Task<TResult> object, and the result of that Task<TResult> object is already computed.

The following example downloads strings from the web. It defines the DownloadStringAsync method. This method downloads strings from the web asynchronously. This example also uses a ConcurrentDictionary<TKey, TValue> object to cache the results of previous operations. If the input address is held in this cache, DownloadStringAsync uses the FromResult<TResult> method to produce a Task<TResult> object that holds the content at that address. Otherwise, DownloadStringAsync downloads the file from the web and adds the result to the cache.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

// Demonstrates how to use Task<TResult>.FromResult to create a task 
// that holds a pre-computed result.
class CachedDownloads
{
   // Holds the results of download operations.
   static ConcurrentDictionary<string, string> cachedDownloads =
      new ConcurrentDictionary<string, string>();

   // Asynchronously downloads the requested resource as a string.
   public static Task<string> DownloadStringAsync(string address)
   {
      // First try to retrieve the content from cache.
      string content;
      if (cachedDownloads.TryGetValue(address, out content))
      {
         return Task.FromResult<string>(content);
      }

      // If the result was not in the cache, download the 
      // string and add it to the cache.
      return Task.Run(async () =>
      {
         content = await new WebClient().DownloadStringTaskAsync(address);
         cachedDownloads.TryAdd(address, content);
         return content;
      });
   }

   static void Main(string[] args)
   {
      // The URLs to download.
      string[] urls = new string[]
      {
         "http://msdn.microsoft.com",
         "http://www.contoso.com",
         "http://www.microsoft.com"
      };

      // Used to time download operations.
      Stopwatch stopwatch = new Stopwatch();

      // Compute the time required to download the URLs.
      stopwatch.Start();
      var downloads = from url in urls
                      select DownloadStringAsync(url);
      Task.WhenAll(downloads).ContinueWith(results =>
      {
         stopwatch.Stop();

         // Print the number of characters download and the elapsed time.
         Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
            results.Result.Sum(result => result.Length),
            stopwatch.ElapsedMilliseconds);
      })
      .Wait();

      // Perform the same operation a second time. The time required
      // should be shorter because the results are held in the cache.
      stopwatch.Restart();
      downloads = from url in urls
                  select DownloadStringAsync(url);
      Task.WhenAll(downloads).ContinueWith(results =>
      {
         stopwatch.Stop();

         // Print the number of characters download and the elapsed time.
         Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
            results.Result.Sum(result => result.Length),
            stopwatch.ElapsedMilliseconds);
      })
      .Wait();
   }
}

/* Sample output:
Retrieved 27798 characters. Elapsed time was 1045 ms.
Retrieved 27798 characters. Elapsed time was 0 ms.
*/

This example computes the time that is required to download multiple strings two times. The second set of download operations should take less time than the first set because the results are held in the cache. The FromResult<TResult> method enables the DownloadStringAsync method to create Task<TResult> objects that hold these pre-computed results.






How to: Traverse a Binary Tree with Parallel Tasks


The following example shows two ways in which parallel tasks can be used to traverse a tree data structure. The creation of the tree itself is left as an exercise.

    public class TreeWalk
    {
        static void Main()
        {
            Tree<MyClass> tree = new Tree<MyClass>();

            // ...populate tree (left as an exercise)

            // Define the Action to perform on each node.
            Action<MyClass> myAction = x => Console.WriteLine("{0} : {1}", x.Name, x.Number);

            // Traverse the tree with parallel tasks.
            DoTree(tree, myAction);
        }

        public class MyClass
        {
            public string Name { get; set; }
            public int Number { get; set; }
        }
        public class Tree<T>
        {
            public Tree<T> Left;
            public Tree<T> Right;
            public T Data;
        }



        // By using tasks explcitly.
        public static void DoTree<T>(Tree<T> tree, Action<T> action)
        {
            if (tree == null) return;
            var left = Task.Factory.StartNew(() => DoTree(tree.Left, action));
            var right = Task.Factory.StartNew(() => DoTree(tree.Right, action));
            action(tree.Data);

            try
            {
                Task.WaitAll(left, right);
            }
            catch (AggregateException )
            {
                //handle exceptions here
            }
        }

        // By using Parallel.Invoke
        public static void DoTree2<T>(Tree<T> tree, Action<T> action)
        {
            if (tree == null) return;
            Parallel.Invoke(
                () => DoTree2(tree.Left, action),
                () => DoTree2(tree.Right, action),
                () => action(tree.Data)
            );
        }

    }

The two methods shown are functionally equivalent. By using the StartNew method to create and run the tasks, you get a handle back from the tasks which can be used to wait on the tasks and handle exceptions.






How to: Unwrap a Nested Task


You can return a task from a method, and then wait on or continue from that task, as shown in the following example:

    static Task<string> DoWorkAsync()
    {
        return Task<String>.Factory.StartNew(() =>
        {
           //...
            return "Work completed.";
        });
    }

    static void StartTask()
    {
        Task<String> t = DoWorkAsync();
        t.Wait();
        Console.WriteLine(t.Result);
    }

In the previous example, the Result property is of type string (String in Visual Basic).

However, in some scenarios, you might want to create a task within another task, and then return the nested task. In this case, the TResult of the enclosing task is itself a task. In the following example, the Result property is a Task<Task<string>> in C# or Task(Of Task(Of String)) in Visual Basic.

        // Note the type of t and t2.
        Task<Task<string>> t = Task.Factory.StartNew(() => DoWorkAsync());
        Task<Task<string>> t2 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync());

        // Outputs: System.Threading.Tasks.Task`1[System.String]
        Console.WriteLine(t.Result);

Although it is possible to write code to unwrap the outer task and retrieve the original task and its Result property, such code is not easy to write because you must handle exceptions and also cancellation requests. In this situation, we recommend that you use one of the Unwrap extension methods, as shown in the following example.

        // Unwrap the inner task.
        Task<string> t3 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync()).Unwrap();
        
        // Outputs "More work completed."
        Console.WriteLine(t.Result);

The Unwrap methods can be used to transform any Task<Task> or Task<Task<TResult>> (Task(Of Task) or Task(Of Task(Of TResult)) in Visual Basic) to a Task or Task<TResult> (Task(Of TResult) in Visual Basic). The new task fully represents the inner nested task, and includes cancellation state and all exceptions.

The following example demonstrates how to use the Unwrap extension methods.

namespace Unwrap
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    // A program whose only use is to demonstrate Unwrap.
    class Program
    {
        static void Main()
        {
            // An arbitrary threshold value.
            byte threshold = 0x40;

            // data is a Task<byte[]>
            var data = Task<byte[]>.Factory.StartNew(() =>
                {
                    return GetData();
                });

            // We want to return a task so that we can
            // continue from it later in the program.
            // Without Unwrap: stepTwo is a Task<Task<byte[]>>
            // With Unwrap: stepTwo is a Task<byte[]>
            var stepTwo = data.ContinueWith((antecedent) =>
                {                    
                    return Task<byte>.Factory.StartNew( () => Compute(antecedent.Result));                  
                })
                .Unwrap();

            // Without Unwrap: antecedent.Result = Task<byte>
            // and the following method will not compile.
            // With Unwrap: antecedent.Result = byte and
            // we can work directly with the result of the Compute method.
            var lastStep = stepTwo.ContinueWith( (antecedant) =>
                {
                    if (antecedant.Result >= threshold)
                    {
                      return Task.Factory.StartNew( () =>  Console.WriteLine("Program complete. Final = 0x{0:x} threshold = 0x{1:x}", stepTwo.Result, threshold));
                    }
                    else
                    {
                        return DoSomeOtherAsyncronousWork(stepTwo.Result, threshold);
                    }
                });

            lastStep.Wait();
            Console.WriteLine("Press any key");
            Console.ReadKey();
        }

        #region Dummy_Methods
        private static byte[] GetData()
        {
            Random rand = new Random();
            byte[] bytes = new byte[64];
            rand.NextBytes(bytes);
            return bytes;
        }

        static Task DoSomeOtherAsyncronousWork(int i, byte b2) 
        {
            return Task.Factory.StartNew(() =>
                {
                    Thread.SpinWait(500000);
                    Console.WriteLine("Doing more work. Value was <= threshold");
                });
        }
        static byte Compute(byte[] data)
        {

            byte final = 0;
            foreach (byte item in data)
            {
                final ^= item;
                Console.WriteLine("{0:x}", final);
            }
            Console.WriteLine("Done computing");
            return final;
        }
        #endregion
    }
}






How to: Prevent a Child Task from Attaching to its Parent


This document demonstrates how to prevent a child task from attaching to the parent task. Preventing a child task from attaching to its parent is useful when you call a component that is written by a third party and that also uses tasks. For example, a third-party component that uses the TaskCreationOptions.AttachedToParent option to create a Task or Task<TResult> object can cause problems in your code if it is long-running or throws an unhandled exception.

The following example compares the effects of using the default options to the effects of preventing a child task from attaching to the parent. The example creates a Task object that calls into a third-party library that also uses a Task object. The third-party library uses the AttachedToParent option to create the Task object. The application uses the TaskCreationOptions.DenyChildAttach option to create the parent task. This option instructs the runtime to remove the AttachedToParent specification in child tasks.

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

// Defines functionality that is provided by a third-party.
// In a real-world scenario, this would likely be provided
// in a separate code file or assembly.
namespace Contoso
{
   public class Widget
   {     
      public Task Run()
      {
         // Create a long-running task that is attached to the 
         // parent in the task hierarchy.
         return Task.Factory.StartNew(() =>
         {
            // Simulate a lengthy operation.
            Thread.Sleep(5000);

         }, TaskCreationOptions.AttachedToParent);
      }
   }
}

// Demonstrates how to prevent a child task from attaching to the parent.
class DenyChildAttach
{
   static void RunWidget(Contoso.Widget widget,
      TaskCreationOptions parentTaskOptions)
   {
      // Record the time required to run the parent
      // and child tasks.
      Stopwatch stopwatch = new Stopwatch();
      stopwatch.Start();

      Console.WriteLine("Starting widget as a background task...");

      // Run the widget task in the background.
      Task<Task> runWidget = Task.Factory.StartNew(() =>
         {
            Task widgetTask = widget.Run();

            // Perform other work while the task runs...
            Thread.Sleep(1000);

            return widgetTask;
         }, parentTaskOptions);
      
      // Wait for the parent task to finish.
      Console.WriteLine("Waiting for parent task to finish...");
      runWidget.Wait();
      Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.", 
         stopwatch.ElapsedMilliseconds);

      // Perform more work...
      Console.WriteLine("Performing more work on the main thread...");
      Thread.Sleep(2000);
      Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds);

      // Wait for the child task to finish.
      Console.WriteLine("Waiting for child task to finish...");
      runWidget.Result.Wait();
      Console.WriteLine("Child task has finished. Elapsed time is {0} ms.",
        stopwatch.ElapsedMilliseconds);
   }
     
   static void Main(string[] args)
   {
      Contoso.Widget w = new Contoso.Widget();

      // Perform the same operation two times. The first time, the operation
      // is performed by using the default task creation options. The second
      // time, the operation is performed by using the DenyChildAttach option
      // in the parent task.

      Console.WriteLine("Demonstrating parent/child tasks with default options...");
      RunWidget(w, TaskCreationOptions.None);

      Console.WriteLine();

      Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...");
      RunWidget(w, TaskCreationOptions.DenyChildAttach);
   }
}

/* Sample output:
Demonstrating parent/child tasks with default options...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 5014 ms.
Performing more work on the main thread...
Elapsed time is 7019 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 7019 ms.

Demonstrating parent/child tasks with the DenyChildAttach option...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 1007 ms.
Performing more work on the main thread...
Elapsed time is 3015 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 5015 ms.
*/

Because a parent task does not finish until all child tasks finish, a long-running child task can cause the overall application to perform poorly. In this example, when the application uses the default options to create the parent task, the child task must finish before the parent task finishes. When the application uses the TaskCreationOptions.DenyChildAttach option, the child is not attached to the parent. Therefore, the application can perform additional work after the parent task finishes and before it must wait for the child task to finish.













'프로그래밍 > C#' 카테고리의 다른 글

Data Parallelism (Task Parallel Library)  (0) 2017.05.06
Task  (0) 2017.04.22
Asynchronous Programming with async and await (C#)  (0) 2017.04.19
Developing Windows Service Applications  (0) 2017.04.02
Events  (0) 2017.03.29
:
Posted by 지훈2