달력

4

« 2025/4 »

  • 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
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


  1. Asynchronous Programming with async and await (C#)
  2. Walkthrough: Accessing the Web by Using async and await (C#)
  3. How to: Extend the async Walkthrough by Using Task.WhenAll (C#)
  4. How to: Make Multiple Web Requests in Parallel by Using async and await (C#)
  5. Async Return Types (C#)
  6. Control Flow in Async Programs (C#)
  7. Fine-Tuning Your Async Application (C#)
  8. Cancel an Async Task or a List of Tasks (C#)
  9. Cancel Async Tasks after a Period of Time (C#)
  10. Cancel Remaining Async Tasks after One Is Complete (C#)
  11. Start Multiple Async Tasks and Process Them As They Complete (C#)
  12. Handling Reentrancy in Async Apps (C#)
  13. Using Async for File Access (C#)


https://msdn.microsoft.com/en-us/library/mt674882.aspx




Asynchronous Programming with async and await (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can avoid performance bottlenecks and enhance the overall responsiveness of your application by using asynchronous programming. However, traditional techniques for writing asynchronous applications can be complicated, making them difficult to write, debug, and maintain.

Visual Studio 2012 introduced a simplified approach, async programming, that leverages asynchronous support in the .NET Framework 4.5 and higher as well as in the Windows Runtime. The compiler does the difficult work that the developer used to do, and your application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort.

This topic provides an overview of when and how to use async programming and includes links to support topics that contain details and examples.

Asynchrony is essential for activities that are potentially blocking, such as when your application accesses the web. Access to a web resource sometimes is slow or delayed. If such an activity is blocked within a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn't depend on the web resource until the potentially blocking task finishes.

The following table shows typical areas where asynchronous programming improves responsiveness. The listed APIs from the .NET Framework 4.5 and the Windows Runtime contain methods that support async programming.

Application areaSupporting APIs that contain async methods
Web accessHttpClientSyndicationClient
Working with filesStorageFileStreamWriterStreamReaderXmlReader
Working with imagesMediaCaptureBitmapEncoderBitmapDecoder
WCF programmingSynchronous and Asynchronous Operations

Asynchrony proves especially valuable for applications that access the UI thread because all UI-related activity usually shares one thread. If any process is blocked in a synchronous application, all are blocked. Your application stops responding, and you might conclude that it has failed when instead it's just waiting.

When you use asynchronous methods, the application continues to respond to the UI. You can resize or minimize a window, for example, or you can close the application if you don't want to wait for it to finish.

The async-based approach adds the equivalent of an automatic transmission to the list of options that you can choose from when designing asynchronous operations. That is, you get all the benefits of traditional asynchronous programming but with much less effort from the developer.

The async and await keywords in C# are the heart of async programming. By using those two keywords, you can use resources in the .NET Framework or the Windows Runtime to create an asynchronous method almost as easily as you create a synchronous method. Asynchronous methods that you define by using async and await are referred to as async methods.

The following example shows an async method. Almost everything in the code should look completely familiar to you. The comments call out the features that you add to create the asynchrony.

You can find a complete Windows Presentation Foundation (WPF) example file at the end of this topic, and you can download the sample from Async Sample: Example from "Asynchronous Programming with Async and Await".

// Three things to note in the signature:  
//  - The method has an async modifier.   
//  - The return type is Task or Task<T>. (See "Return Types" section.)  
//    Here, it is Task<int> because the return statement returns an integer.  
//  - The method name ends in "Async."  
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  
  
    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
  
    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  
  
    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from getStringTask.  
    string urlContents = await getStringTask;  
  
    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
    return urlContents.Length;  
}  

If AccessTheWebAsync doesn't have any work that it can do between calling GetStringAsync and awaiting its completion, you can simplify your code by calling and awaiting in the following single statement.

string urlContents = await client.GetStringAsync();  

The following characteristics summarize what makes the previous example an async method.

  • The method signature includes an async modifier.

  • The name of an async method, by convention, ends with an "Async" suffix.

  • The return type is one of the following types:

    • Task<TResult> if your method has a return statement in which the operand has type TResult.

    • Task if your method has no return statement or has a return statement with no operand.

    • Void if you're writing an async event handler.

    For more information, see "Return Types and Parameters" later in this topic.

  • The method usually includes at least one await expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspended, and control returns to the method's caller. The next section of this topic illustrates what happens at the suspension point.

In async methods, you use the provided keywords and types to indicate what you want to do, and the compiler does the rest, including keeping track of what must happen when control returns to an await point in a suspended method. Some routine processes, such as loops and exception handling, can be difficult to handle in traditional asynchronous code. In an async method, you write these elements much as you would in a synchronous solution, and the problem is solved.

For more information about asynchrony in previous versions of the .NET Framework, see TPL and Traditional .NET Framework Asynchronous Programming.

The most important thing to understand in asynchronous programming is how the control flow moves from method to method. The following diagram leads you through the process.

Trace an async program

The numbers in the diagram correspond to the following steps.

  1. An event handler calls and awaits the AccessTheWebAsync async method.

  2. AccessTheWebAsync creates an HttpClient instance and calls the GetStringAsync asynchronous method to download the contents of a website as a string.

  3. Something happens in GetStringAsync that suspends its progress. Perhaps it must wait for a website to download or some other blocking activity. To avoid blocking resources, GetStringAsync yields control to its caller, AccessTheWebAsync.

    GetStringAsync returns a Task<TResult> where TResult is a string, and AccessTheWebAsync assigns the task to the getStringTask variable. The task represents the ongoing process for the call to GetStringAsync, with a commitment to produce an actual string value when the work is complete.

  4. Because getStringTask hasn't been awaited yet, AccessTheWebAsync can continue with other work that doesn't depend on the final result from GetStringAsync. That work is represented by a call to the synchronous method DoIndependentWork.

  5. DoIndependentWork is a synchronous method that does its work and returns to its caller.

  6. AccessTheWebAsync has run out of work that it can do without a result from getStringTaskAccessTheWebAsync next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string.

    Therefore, AccessTheWebAsync uses an await operator to suspend its progress and to yield control to the method that called AccessTheWebAsyncAccessTheWebAsync returns a Task<int> to the caller. The task represents a promise to produce an integer result that's the length of the downloaded string.

    System_CAPS_ICON_note.jpg Note

    If GetStringAsync (and therefore getStringTask) is complete before AccessTheWebAsync awaits it, control remains in AccessTheWebAsync. The expense of suspending and then returning to AccessTheWebAsync would be wasted if the called asynchronous process (getStringTask) has already completed and AccessTheWebSync doesn't have to wait for the final result.

    Inside the caller (the event handler in this example), the processing pattern continues. The caller might do other work that doesn't depend on the result from AccessTheWebAsync before awaiting that result, or the caller might await immediately. The event handler is waiting for AccessTheWebAsync, and AccessTheWebAsync is waiting for GetStringAsync.

  7. GetStringAsync completes and produces a string result. The string result isn't returned by the call to GetStringAsync in the way that you might expect. (Remember that the method already returned a task in step 3.) Instead, the string result is stored in the task that represents the completion of the method, getStringTask. The await operator retrieves the result from getStringTask. The assignment statement assigns the retrieved result to urlContents.

  8. When AccessTheWebAsync has the string result, the method can calculate the length of the string. Then the work of AccessTheWebAsync is also complete, and the waiting event handler can resume. In the full example at the end of the topic, you can confirm that the event handler retrieves and prints the value of the length result.

If you are new to asynchronous programming, take a minute to consider the difference between synchronous and asynchronous behavior. A synchronous method returns when its work is complete (step 5), but an async method returns a task value when its work is suspended (steps 3 and 6). When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task.

For more information about control flow, see Control Flow in Async Programs (C#).

You might be wondering where to find methods such as GetStringAsync that support async programming. The .NET Framework 4.5 or higher contains many members that work with async and await. You can recognize these members by the "Async" suffix that’s attached to the member name and a return type of Task or Task<TResult>. For example, the System.IO.Stream class contains methods such as CopyToAsyncReadAsync, and WriteAsync alongside the synchronous methods CopyToRead, and Write.

The Windows Runtime also contains many methods that you can use with async and await in Windows apps. For more information and example methods, see Quickstart: using the await operator for asynchronous programmingAsynchronous programming (Windows Store apps), and WhenAny: Bridging between the .NET Framework and the Windows Runtime (C#).

Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.

The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool.

If you specify that a method is an async method by using an async modifier, you enable the following two capabilities.

  • The marked async method can use await to designate suspension points. The await operator tells the compiler that the async method can't continue past that point until the awaited asynchronous process is complete. In the meantime, control returns to the caller of the async method.

    The suspension of an async method at an await expression doesn't constitute an exit from the method, and finally blocks don’t run.

  • The marked async method can itself be awaited by methods that call it.

An async method typically contains one or more occurrences of an await operator, but the absence of await expressions doesn’t cause a compiler error. If an async method doesn’t use an await operator to mark a suspension point, the method executes as a synchronous method does, despite the asyncmodifier. The compiler issues a warning for such methods.

async and await are contextual keywords. For more information and examples, see the following topics:

In .NET Framework programming, an async method typically returns a Task or a Task<TResult>. Inside an async method, an await operator is applied to a task that's returned from a call to another async method.

You specify Task<TResult> as the return type if the method contains a return statement that specifies an operand of type TResult.

You use Task as the return type if the method has no return statement or has a return statement that doesn't return an operand.

The following example shows how you declare and call a method that returns a Task<TResult> or a Task.

// Signature specifies Task<TResult>  
async Task<int> TaskOfTResult_MethodAsync()  
{  
    int hours;  
    // . . .  
    // Return statement specifies an integer result.  
    return hours;  
}  
  
// Calls to TaskOfTResult_MethodAsync  
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();  
int intResult = await returnedTaskTResult;  
// or, in a single statement  
int intResult = await TaskOfTResult_MethodAsync();  
  
// Signature specifies Task  
async Task Task_MethodAsync()  
{  
    // . . .  
    // The method has no return statement.    
}  
  
// Calls to Task_MethodAsync  
Task returnedTask = Task_MethodAsync();  
await returnedTask;  
// or, in a single statement  
await Task_MethodAsync();  
  

Each returned task represents ongoing work. A task encapsulates information about the state of the asynchronous process and, eventually, either the final result from the process or the exception that the process raises if it doesn't succeed.

An async method can also have a void return type. This return type is used primarily to define event handlers, where a void return type is required. Async event handlers often serve as the starting point for async programs.

An async method that has a void return type can’t be awaited, and the caller of a void-returning method can't catch any exceptions that the method throws.

An async method can't declare ref or out parameters, but the method can call methods that have such parameters.

For more information and examples, see Async Return Types (C#). For more information about how to catch exceptions in async methods, see try-catch.

Asynchronous APIs in Windows Runtime programming have one of the following return types, which are similar to tasks:

For more information and an example, see Quickstart: using the await operator for asynchronous programming.

By convention, you append "Async" to the names of methods that have an async modifier.

You can ignore the convention where an event, base class, or interface contract suggests a different name. For example, you shouldn’t rename common event handlers, such as Button1_Click.

TitleDescriptionSample
Walkthrough: Accessing the Web by Using async and await (C#)Shows how to convert a synchronous WPF solution to an asynchronous WPF solution. The application downloads a series of websites.Async Sample: Accessing the Web Walkthrough
How to: Extend the async Walkthrough by Using Task.WhenAll (C#)Adds Task.WhenAll to the previous walkthrough. The use of WhenAll starts all the downloads at the same time.
How to: Make Multiple Web Requests in Parallel by Using async and await (C#)Demonstrates how to start several tasks at the same time.Async Sample: Make Multiple Web Requests in Parallel
Async Return Types (C#)Illustrates the types that async methods can return and explains when each type is appropriate.
Control Flow in Async Programs (C#)Traces in detail the flow of control through a succession of await expressions in an asynchronous program.Async Sample: Control Flow in Async Programs
Fine-Tuning Your Async Application (C#)Shows how to add the following functionality to your async solution:

Cancel an Async Task or a List of Tasks (C#)
Cancel Async Tasks after a Period of Time (C#)
Cancel Remaining Async Tasks after One Is Complete (C#)
Start Multiple Async Tasks and Process Them As They Complete (C#)
Async Sample: Fine Tuning Your Application
Handling Reentrancy in Async Apps (C#)Shows how to handle cases in which an active asynchronous operation is restarted while it’s running.
WhenAny: Bridging between the .NET Framework and the Windows Runtime (C#)Shows how to bridge between Task types in the .NET Framework and IAsyncOperations in the Windows Runtime so that you can use WhenAny<TResult> with a Windows Runtime method.Async Sample: Bridging between .NET and Windows Runtime (AsTask and WhenAny)
Async Cancellation: Bridging between the .NET Framework and the Windows Runtime (C#)Shows how to bridge between Task types in the .NET Framework and IAsyncOperations in the Windows Runtime so that you can use CancellationTokenSource with a Windows Runtime method.Async Sample: Bridging between .NET and Windows Runtime (AsTask & Cancellation)
Using Async for File Access (C#)Lists and demonstrates the benefits of using async and await to access files.
Task-based Asynchronous Pattern (TAP)Describes a new pattern for asynchrony in the .NET Framework. The pattern is based on the Task and Task<TResult> types.
Async Videos on Channel 9Provides links to a variety of videos about async programming.

The following code is the MainWindow.xaml.cs file from the Windows Presentation Foundation (WPF) application that this topic discusses. You can download the sample from Async Sample: Example from "Asynchronous Programming with Async and Await".

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http;  
using System.Net.Http;  
  
namespace AsyncFirstExample  
{  
    public partial class MainWindow : Window  
    {  
        // Mark the event handler with async so you can use await in it.  
        private async void StartButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Call and await separately.  
            //Task<int> getLengthTask = AccessTheWebAsync();  
            //// You can do independent work here.  
            //int contentLength = await getLengthTask;  
  
            int contentLength = await AccessTheWebAsync();  
  
            resultsTextBox.Text +=  
                String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
        }  
  
        // Three things to note in the signature:  
        //  - The method has an async modifier.   
        //  - The return type is Task or Task<T>. (See "Return Types" section.)  
        //    Here, it is Task<int> because the return statement returns an integer.  
        //  - The method name ends in "Async."  
        async Task<int> AccessTheWebAsync()  
        {   
            // You need to add a reference to System.Net.Http to declare client.  
            HttpClient client = new HttpClient();  
  
            // GetStringAsync returns a Task<string>. That means that when you await the  
            // task you'll get a string (urlContents).  
            Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
  
            // You can do work here that doesn't rely on the string from GetStringAsync.  
            DoIndependentWork();  
  
            // The await operator suspends AccessTheWebAsync.  
            //  - AccessTheWebAsync can't continue until getStringTask is complete.  
            //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
            //  - Control resumes here when getStringTask is complete.   
            //  - The await operator then retrieves the string result from getStringTask.  
            string urlContents = await getStringTask;  
  
            // The return statement specifies an integer result.  
            // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
            return urlContents.Length;  
        }  
  
        void DoIndependentWork()  
        {  
            resultsTextBox.Text += "Working . . . . . . .\r\n";  
        }  
    }  
}  
  
// Sample Output:  
  
// Working . . . . . . .  
  
// Length of the downloaded string: 41564.






Walkthrough: Accessing the Web by Using async and await (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can write asynchronous programs more easily and intuitively by using features that were introduced in Visual Studio 2012. You can write asynchronous code that looks like synchronous code and let the compiler handle the difficult callback functions and continuations that asynchronous code usually entails.

For more information about the Async feature, see Asynchronous Programming with async and await (C#).

This walkthrough starts with a synchronous Windows Presentation Foundation (WPF) application that sums the number of bytes in a list of websites. The walkthrough then converts the application to an asynchronous solution by using the new features.

If you don't want to build the applications yourself, you can download "Async Sample: Accessing the Web Walkthrough (C# and Visual Basic)" from Developer Code Samples.

In this walkthrough, you complete the following tasks:

Visual Studio 2012 or later must be installed on your computer. For more information, see the Microsoft website.

To create a WPF application

  1. Start Visual Studio.

  2. On the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  3. In the Installed Templates pane, choose Visual C#, and then choose WPF Application from the list of project types.

  4. In the Name text box, enter AsyncExampleWPF, and then choose the OK button.

    The new project appears in Solution Explorer.

To design a simple WPF MainWindow

  1. In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

  2. If the Toolbox window isn’t visible, open the View menu, and then choose Toolbox.

  3. Add a Button control and a TextBox control to the MainWindow window.

  4. Highlight the TextBox control and, in the Properties window, set the following values:

    • Set the Name property to resultsTextBox.

    • Set the Height property to 250.

    • Set the Width property to 500.

    • On the Text tab, specify a monospaced font, such as Lucida Console or Global Monospace.

  5. Highlight the Button control and, in the Properties window, set the following values:

    • Set the Name property to startButton.

    • Change the value of the Content property from Button to Start.

  6. Position the text box and the button so that both appear in the MainWindow window.

    For more information about the WPF XAML Designer, see Creating a UI by using XAML Designer.

To add a reference

  1. In Solution Explorer, highlight your project's name.

  2. On the menu bar, choose ProjectAdd Reference.

    The Reference Manager dialog box appears.

  3. At the top of the dialog box, verify that your project is targeting the .NET Framework 4.5 or higher.

  4. In the Assemblies area, choose Framework if it isn’t already chosen.

  5. In the list of names, select the System.Net.Http check box.

  6. Choose the OK button to close the dialog box.

To add necessary using directives

  1. In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  2. Add the following using directives at the top of the code file if they’re not already present.

    using System.Net.Http;  
    using System.Net;  
    using System.IO;  
    
    

To create a synchronous application

  1. In the design window, MainWindow.xaml, double-click the Start button to create the startButton_Click event handler in MainWindow.xaml.cs.

  2. In MainWindow.xaml.cs, copy the following code into the body of startButton_Click:

    resultsTextBox.Clear();  
    SumPageSizes();  
    resultsTextBox.Text += "\r\nControl returned to startButton_Click.";  
    
    

    The code calls the method that drives the application, SumPageSizes, and displays a message when control returns to startButton_Click.

  3. The code for the synchronous solution contains the following four methods:

    • SumPageSizes, which gets a list of webpage URLs from SetUpURLList and then calls GetURLContents and DisplayResults to process each URL.

    • SetUpURLList, which makes and returns a list of web addresses.

    • GetURLContents, which downloads the contents of each website and returns the contents as a byte array.

    • DisplayResults, which displays the number of bytes in the byte array for each URL.

    Copy the following four methods, and then paste them under the startButton_Click event handler in MainWindow.xaml.cs:

    private void SumPageSizes()  
    {  
        // Make a list of web addresses.  
        List<string> urlList = SetUpURLList();   
    
        var total = 0;  
        foreach (var url in urlList)  
        {  
            // GetURLContents returns the contents of url as a byte array.  
            byte[] urlContents = GetURLContents(url);  
    
            DisplayResults(url, urlContents);  
    
            // Update the total.  
            total += urlContents.Length;  
        }  
    
        // Display the total count for all of the web addresses.  
        resultsTextBox.Text +=   
            string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
    }  
    
    private List<string> SetUpURLList()  
    {  
        var urls = new List<string>   
        {   
            "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
            "http://msdn.microsoft.com",  
            "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
            "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
            "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
            "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
            "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
            "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
            "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
            "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
        };  
        return urls;  
    }  
    
    private byte[] GetURLContents(string url)  
    {  
        // The downloaded resource ends up in the variable named content.  
        var content = new MemoryStream();  
    
        // Initialize an HttpWebRequest for the current URL.  
        var webReq = (HttpWebRequest)WebRequest.Create(url);  
    
        // Send the request to the Internet resource and wait for  
        // the response.  
        // Note: you can't use HttpWebRequest.GetResponse in a Windows Store app.  
        using (WebResponse response = webReq.GetResponse())  
        {  
            // Get the data stream that is associated with the specified URL.  
            using (Stream responseStream = response.GetResponseStream())  
            {  
                // Read the bytes in responseStream and copy them to content.    
                responseStream.CopyTo(content);  
            }  
        }  
    
        // Return the result as a byte array.  
        return content.ToArray();  
    }  
    
    private void DisplayResults(string url, byte[] content)  
    {  
        // Display the length of each website. The string format   
        // is designed to be used with a monospaced font, such as  
        // Lucida Console or Global Monospace.  
        var bytes = content.Length;  
        // Strip off the "http://".  
        var displayURL = url.Replace("http://", "");  
        resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
    }  
    
    

To test the synchronous solution

  1. Choose the F5 key to run the program, and then choose the Start button.

    Output that resembles the following list should appear.

    msdn.microsoft.com/library/windows/apps/br211380.aspx        383832  
    msdn.microsoft.com                                            33964  
    msdn.microsoft.com/library/hh290136.aspx               225793  
    msdn.microsoft.com/library/ee256749.aspx               143577  
    msdn.microsoft.com/library/hh290138.aspx               237372  
    msdn.microsoft.com/library/hh290140.aspx               128279  
    msdn.microsoft.com/library/dd470362.aspx               157649  
    msdn.microsoft.com/library/aa578028.aspx               204457  
    msdn.microsoft.com/library/ms404677.aspx               176405  
    msdn.microsoft.com/library/ff730837.aspx               143474  
    
    Total bytes returned:  1834802  
    
    Control returned to startButton_Click.  
    
    
    

    Notice that it takes a few seconds to display the counts. During that time, the UI thread is blocked while it waits for requested resources to download. As a result, you can't move, maximize, minimize, or even close the display window after you choose the Start button. These efforts fail until the byte counts start to appear. If a website isn’t responding, you have no indication of which site failed. It is difficult even to stop waiting and close the program.

To convert GetURLContents to an asynchronous method

  1. To convert the synchronous solution to an asynchronous solution, the best place to start is in GetURLContents because the calls to the HttpWebRequest method GetResponse and to the Stream method CopyTo are where the application accesses the web. The .NET Framework makes the conversion easy by supplying asynchronous versions of both methods.

    For more information about the methods that are used in GetURLContents, see WebRequest.

    System_CAPS_ICON_note.jpg Note

    As you follow the steps in this walkthrough, several compiler errors appear. You can ignore them and continue with the walkthrough.

    Change the method that's called in the third line of GetURLContents from GetResponse to the asynchronous, task-based GetResponseAsync method.

    using (WebResponse response = webReq.GetResponseAsync())  
    
    
  2. GetResponseAsync returns a Task<TResult>. In this case, the task return variableTResult, has type WebResponse. The task is a promise to produce an actual WebResponse object after the requested data has been downloaded and the task has run to completion.

    To retrieve the WebResponse value from the task, apply an await operator to the call to GetResponseAsync, as the following code shows.

    using (WebResponse response = await webReq.GetResponseAsync())  
    
    

    The await operator suspends the execution of the current method, GetURLContents, until the awaited task is complete. In the meantime, control returns to the caller of the current method. In this example, the current method is GetURLContents, and the caller is SumPageSizes. When the task is finished, the promised WebResponse object is produced as the value of the awaited task and assigned to the variable response.

    The previous statement can be separated into the following two statements to clarify what happens.

    //Task<WebResponse> responseTask = webReq.GetResponseAsync();  
    //using (WebResponse response = await responseTask)  
    
    

    The call to webReq.GetResponseAsync returns a Task(Of WebResponse) or Task<WebResponse>. Then an await operator is applied to the task to retrieve the WebResponse value.

    If your async method has work to do that doesn’t depend on the completion of the task, the method can continue with that work between these two statements, after the call to the async method and before the await operator is applied. For examples, see How to: Make Multiple Web Requests in Parallel by Using async and await (C#) and How to: Extend the async Walkthrough by Using Task.WhenAll (C#).

  3. Because you added the await operator in the previous step, a compiler error occurs. The operator can be used only in methods that are marked with the async modifier. Ignore the error while you repeat the conversion steps to replace the call to CopyTo with a call to CopyToAsync.

    • Change the name of the method that’s called to CopyToAsync.

    • The CopyTo or CopyToAsync method copies bytes to its argument, content, and doesn’t return a meaningful value. In the synchronous version, the call to CopyTo is a simple statement that doesn't return a value. The asynchronous version, CopyToAsync, returns a Task. The task functions like "Task(void)" and enables the method to be awaited. Apply Await or await to the call to CopyToAsync, as the following code shows.

      await responseStream.CopyToAsync(content);  
      
      

      The previous statement abbreviates the following two lines of code.

      // CopyToAsync returns a Task, not a Task<T>.  
      //Task copyTask = responseStream.CopyToAsync(content);  
      
      // When copyTask is completed, content contains a copy of  
      // responseStream.  
      //await copyTask;  
      
      
  4. All that remains to be done in GetURLContents is to adjust the method signature. You can use the await operator only in methods that are marked with the async modifier. Add the modifier to mark the method as an async method, as the following code shows.

    private async byte[] GetURLContents(string url)  
    
    
    
  5. The return type of an async method can only be TaskTask<TResult>, or void in C#. Typically, a return type of void is used only in an async event handler, where void is required. In other cases, you use Task(T) if the completed method has a return statement that returns a value of type T, and you use Task if the completed method doesn’t return a meaningful value. You can think of the Task return type as meaning "Task(void)."

    For more information, see Async Return Types (C#).

    Method GetURLContents has a return statement, and the statement returns a byte array. Therefore, the return type of the async version is Task(T), where T is a byte array. Make the following changes in the method signature:

    • Change the return type to Task<byte[]>.

    • By convention, asynchronous methods have names that end in "Async," so rename the method GetURLContentsAsync.

    The following code shows these changes.

    private async Task<byte[]> GetURLContentsAsync(string url)  
    
    

    With those few changes, the conversion of GetURLContents to an asynchronous method is complete.

To convert SumPageSizes to an asynchronous method

  1. Repeat the steps from the previous procedure for SumPageSizes. First, change the call to GetURLContents to an asynchronous call.

    • Change the name of the method that’s called from GetURLContents to GetURLContentsAsync, if you haven't already done so.

    • Apply await to the task that GetURLContentsAsync returns to obtain the byte array value.

    The following code shows these changes.

    byte[] urlContents = await GetURLContentsAsync(url);  
    
    

    The previous assignment abbreviates the following two lines of code.

    // GetURLContentsAsync returns a Task<T>. At completion, the task  
    // produces a byte array.  
    //Task<byte[]> getContentsTask = GetURLContentsAsync(url);  
    //byte[] urlContents = await getContentsTask;  
    
    
  2. Make the following changes in the method's signature:

    • Mark the method with the async modifier.

    • Add "Async" to the method name.

    • There is no task return variable, T, this time because SumPageSizesAsync doesn’t return a value for T. (The method has no returnstatement.) However, the method must return a Task to be awaitable. Therefore, change the return type of the method from void to Task.

    The following code shows these changes.

    private async Task SumPageSizesAsync()  
    
    

    The conversion of SumPageSizes to SumPageSizesAsync is complete.

To convert startButton_Click to an asynchronous method

  1. In the event handler, change the name of the called method from SumPageSizes to SumPageSizesAsync, if you haven’t already done so.

  2. Because SumPageSizesAsync is an async method, change the code in the event handler to await the result.

    The call to SumPageSizesAsync mirrors the call to CopyToAsync in GetURLContentsAsync. The call returns a Task, not a Task(T).

    As in previous procedures, you can convert the call by using one statement or two statements. The following code shows these changes.

    // One-step async call.  
    await SumPageSizesAsync();  
    
    // Two-step async call.  
    //Task sumTask = SumPageSizesAsync();  
    //await sumTask;  
    
    
  3. To prevent accidentally reentering the operation, add the following statement at the top of startButton_Click to disable the Start button.

    // Disable the button until the operation is complete.  
    startButton.IsEnabled = false;  
    
    

    You can reenable the button at the end of the event handler.

    // Reenable the button in case you want to run the operation again.  
    startButton.IsEnabled = true;  
    
    

    For more information about reentrancy, see Handling Reentrancy in Async Apps (C#).

  4. Finally, add the async modifier to the declaration so that the event handler can await SumPagSizesAsync.

    private async void startButton_Click(object sender, RoutedEventArgs e)  
    
    

    Typically, the names of event handlers aren’t changed. The return type isn’t changed to Task because event handlers must return void.

    The conversion of the project from synchronous to asynchronous processing is complete.

To test the asynchronous solution

  1. Choose the F5 key to run the program, and then choose the Start button.

  2. Output that resembles the output of the synchronous solution should appear. However, notice the following differences.

    • The results don’t all occur at the same time, after the processing is complete. For example, both programs contain a line in startButton_Click that clears the text box. The intent is to clear the text box between runs if you choose the Start button for a second time, after one set of results has appeared. In the synchronous version, the text box is cleared just before the counts appear for the second time, when the downloads are completed and the UI thread is free to do other work. In the asynchronous version, the text box clears immediately after you choose the Start button.

    • Most importantly, the UI thread isn’t blocked during the downloads. You can move or resize the window while the web resources are being downloaded, counted, and displayed. If one of the websites is slow or not responding, you can cancel the operation by choosing the Close button (the x in the red field in the upper-right corner).

To replace method GetURLContentsAsync with a .NET Framework method

  1. The .NET Framework 4.5 provides many async methods that you can use. One of them, the HttpClient method GetByteArrayAsync(String), does just what you need for this walkthrough. You can use it instead of the GetURLContentsAsync method that you created in an earlier procedure.

    The first step is to create an HttpClient object in method SumPageSizesAsync. Add the following declaration at the start of the method.

    // Declare an HttpClient object and increase the buffer size. The  
    // default buffer size is 65,536.  
    HttpClient client =  
        new HttpClient() { MaxResponseContentBufferSize = 1000000 };  
    
    
  2. In SumPageSizesAsync, replace the call to your GetURLContentsAsync method with a call to the HttpClient method.

    byte[] urlContents = await client.GetByteArrayAsync(url);  
    
    
  3. Remove or comment out the GetURLContentsAsync method that you wrote.

  4. Choose the F5 key to run the program, and then choose the Start button.

    The behavior of this version of the project should match the behavior that the "To test the asynchronous solution" procedure describes but with even less effort from you.

The following code contains the full example of the conversion from a synchronous to an asynchronous solution by using the asynchronous GetURLContentsAsync method that you wrote. Notice that it strongly resembles the original, synchronous solution.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add the following using directives, and add a reference for System.Net.Http.  
using System.Net.Http;  
using System.IO;  
using System.Net;  
  
namespace AsyncExampleWPF  
{  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Disable the button until the operation is complete.  
            startButton.IsEnabled = false;  
  
            resultsTextBox.Clear();  
  
            // One-step async call.  
            await SumPageSizesAsync();  
  
            // Two-step async call.  
            //Task sumTask = SumPageSizesAsync();  
            //await sumTask;  
  
            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";  
  
            // Reenable the button in case you want to run the operation again.  
            startButton.IsEnabled = true;  
        }  
  
        private async Task SumPageSizesAsync()  
        {  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            var total = 0;  
  
            foreach (var url in urlList)  
            {  
                byte[] urlContents = await GetURLContentsAsync(url);  
  
                // The previous line abbreviates the following two assignment statements.  
  
                // GetURLContentsAsync returns a Task<T>. At completion, the task  
                // produces a byte array.  
                //Task<byte[]> getContentsTask = GetURLContentsAsync(url);  
                //byte[] urlContents = await getContentsTask;  
  
                DisplayResults(url, urlContents);  
  
                // Update the total.            
                total += urlContents.Length;  
            }  
            // Display the total count for all of the websites.  
            resultsTextBox.Text +=  
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
  
        private async Task<byte[]> GetURLContentsAsync(string url)  
        {  
            // The downloaded resource ends up in the variable named content.  
            var content = new MemoryStream();  
  
            // Initialize an HttpWebRequest for the current URL.  
            var webReq = (HttpWebRequest)WebRequest.Create(url);  
  
            // Send the request to the Internet resource and wait for  
            // the response.                  
            using (WebResponse response = await webReq.GetResponseAsync())  
  
            // The previous statement abbreviates the following two statements.  
  
            //Task<WebResponse> responseTask = webReq.GetResponseAsync();  
            //using (WebResponse response = await responseTask)  
            {  
                // Get the data stream that is associated with the specified url.  
                using (Stream responseStream = response.GetResponseStream())  
                {  
                    // Read the bytes in responseStream and copy them to content.   
                    await responseStream.CopyToAsync(content);  
  
                    // The previous statement abbreviates the following two statements.  
  
                    // CopyToAsync returns a Task, not a Task<T>.  
                    //Task copyTask = responseStream.CopyToAsync(content);  
  
                    // When copyTask is completed, content contains a copy of  
                    // responseStream.  
                    //await copyTask;  
                }  
            }  
            // Return the result as a byte array.  
            return content.ToArray();  
        }  
  
        private void DisplayResults(string url, byte[] content)  
        {  
            // Display the length of each website. The string format   
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;  
            // Strip off the "http://".  
            var displayURL = url.Replace("http://", "");  
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
        }  
    }  
}  

The following code contains the full example of the solution that uses the HttpClient method, GetByteArrayAsync.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add the following using directives, and add a reference for System.Net.Http.  
using System.Net.Http;  
using System.IO;  
using System.Net;  
  
namespace AsyncExampleWPF  
{  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            resultsTextBox.Clear();  
  
            // Disable the button until the operation is complete.  
            startButton.IsEnabled = false;  
  
            // One-step async call.  
            await SumPageSizesAsync();  
  
            //// Two-step async call.  
            //Task sumTask = SumPageSizesAsync();  
            //await sumTask;  
  
            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";  
  
            // Reenable the button in case you want to run the operation again.  
            startButton.IsEnabled = true;  
        }  
  
        private async Task SumPageSizesAsync()  
        {  
            // Declare an HttpClient object and increase the buffer size. The  
            // default buffer size is 65,536.  
            HttpClient client =  
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };  
  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            var total = 0;  
  
            foreach (var url in urlList)  
            {  
                // GetByteArrayAsync returns a task. At completion, the task  
                // produces a byte array.  
                byte[] urlContents = await client.GetByteArrayAsync(url);                 
  
                // The following two lines can replace the previous assignment statement.  
                //Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);  
                //byte[] urlContents = await getContentsTask;  
  
                DisplayResults(url, urlContents);  
  
                // Update the total.  
                total += urlContents.Length;  
            }  
  
            // Display the total count for all of the websites.  
            resultsTextBox.Text +=  
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
  
        private void DisplayResults(string url, byte[] content)  
        {  
            // Display the length of each website. The string format   
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;  
            // Strip off the "http://".  
            var displayURL = url.Replace("http://", "");  
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
        }  
    }  
}






How to: Extend the async Walkthrough by Using Task.WhenAll (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can improve the performance of the async solution in Walkthrough: Accessing the Web by Using async and await (C#) by using the Task.WhenAll method. This method asynchronously awaits multiple asynchronous operations, which are represented as a collection of tasks.

You might have noticed in the walkthrough that the websites download at different rates. Sometimes one of the websites is very slow, which delays all the remaining downloads. When you run the asynchronous solutions that you build in the walkthrough, you can end the program easily if you don't want to wait, but a better option would be to start all the downloads at the same time and let faster downloads continue without waiting for the one that’s delayed.

You apply the Task.WhenAll method to a collection of tasks. The application of WhenAll returns a single task that isn’t complete until every task in the collection is completed. The tasks appear to run in parallel, but no additional threads are created. The tasks can complete in any order.

System_CAPS_ICON_important.jpg Important

The following procedures describe extensions to the async applications that are developed in Walkthrough: Accessing the Web by Using async and await (C#). You can develop the applications by either completing the walkthrough or downloading the code from Developer Code Samples.

To run the example, you must have Visual Studio 2012 or later installed on your computer.

To add Task.WhenAll to your GetURLContentsAsync solution

  1. Add the ProcessURLAsync method to the first application that's developed in Walkthrough: Accessing the Web by Using async and await (C#).

    • If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough project, and then add ProcessURLAsync to the MainWindow.xaml.cs file.

    • If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that includes the GetURLContentsAsync method. The MainWindow.xaml.cs file for this application is the first example in the "Complete Code Examples from the Walkthrough" section.

    The ProcessURLAsync method consolidates the actions in the body of the foreach loop in SumPageSizesAsync in the original walkthrough. The method asynchronously downloads the contents of a specified website as a byte array, and then displays and returns the length of the byte array.

    private async Task<int> ProcessURLAsync(string url)  
    {  
        var byteArray = await GetURLContentsAsync(url);  
        DisplayResults(url, byteArray);  
        return byteArray.Length;  
    }  
    
    
  2. Comment out or delete the foreach loop in SumPageSizesAsync, as the following code shows.

    //var total = 0;  
    //foreach (var url in urlList)  
    //{  
    //    byte[] urlContents = await GetURLContentsAsync(url);  
    
    //    // The previous line abbreviates the following two assignment statements.  
    //    // GetURLContentsAsync returns a Task<T>. At completion, the task  
    //    // produces a byte array.  
    //    //Task<byte[]> getContentsTask = GetURLContentsAsync(url);  
    //    //byte[] urlContents = await getContentsTask;  
    
    //    DisplayResults(url, urlContents);  
    
    //    // Update the total.            
    //    total += urlContents.Length;  
    //}  
    
    
  3. Create a collection of tasks. The following code defines a query that, when executed by the ToArray<TSource> method, creates a collection of tasks that download the contents of each website. The tasks are started when the query is evaluated.

    Add the following code to method SumPageSizesAsync after the declaration of urlList.

    // Create a query.   
    IEnumerable<Task<int>> downloadTasksQuery =   
        from url in urlList select ProcessURLAsync(url);  
    
    // Use ToArray to execute the query and start the download tasks.  
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
    
    
  4. Apply Task.WhenAll to the collection of tasks, downloadTasksTask.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    In the following example, the await expression awaits the completion of the single task that WhenAll returns. The expression evaluates to an array of integers, where each integer is the length of a downloaded website. Add the following code to SumPageSizesAsync, just after the code that you added in the previous step.

    // Await the completion of all the running tasks.  
    int[] lengths = await Task.WhenAll(downloadTasks);  
    
    //// The previous line is equivalent to the following two statements.  
    //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);  
    //int[] lengths = await whenAllTask;  
    
    
  5. Finally, use the Sum method to calculate the sum of the lengths of all the websites. Add the following line to SumPageSizesAsync.

    int total = lengths.Sum();  
    
    

To add Task.WhenAll to the HttpClient.GetByteArrayAsync solution

  1. Add the following version of ProcessURLAsync to the second application that's developed in Walkthrough: Accessing the Web by Using async and await (C#).

    • If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough_HttpClient project, and then add ProcessURLAsync to the MainWindow.xaml.cs file.

    • If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that uses the HttpClient.GetByteArrayAsync method. The MainWindow.xaml.cs file for this application is the second example in the "Complete Code Examples from the Walkthrough" section.

    The ProcessURLAsync method consolidates the actions in the body of the foreach loop in SumPageSizesAsync in the original walkthrough. The method asynchronously downloads the contents of a specified website as a byte array, and then displays and returns the length of the byte array.

    The only difference from the ProcessURLAsync method in the previous procedure is the use of the HttpClient instance, client.

    async Task<int> ProcessURL(string url, HttpClient client)  
    {  
        byte[] byteArray = await client.GetByteArrayAsync(url);  
        DisplayResults(url, byteArray);  
        return byteArray.Length;  
    }  
    
    
  2. Comment out or delete the For Each or foreach loop in SumPageSizesAsync, as the following code shows.

    //var total = 0;  
    //foreach (var url in urlList)  
    //{  
    //    // GetByteArrayAsync returns a Task<T>. At completion, the task  
    //    // produces a byte array.  
    //    byte[] urlContent = await client.GetByteArrayAsync(url);  
    
    //    // The previous line abbreviates the following two assignment  
    //    // statements.  
    //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url);  
    //    byte[] urlContent = await getContentTask;  
    
    //    DisplayResults(url, urlContent);  
    
    //    // Update the total.  
    //    total += urlContent.Length;  
    //}  
    
    
  3. Define a query that, when executed by the ToArray<TSource> method, creates a collection of tasks that download the contents of each website. The tasks are started when the query is evaluated.

    Add the following code to method SumPageSizesAsync after the declaration of client and urlList.

    // Create a query.  
    IEnumerable<Task<int>> downloadTasksQuery =   
        from url in urlList select ProcessURL(url, client);  
    
    // Use ToArray to execute the query and start the download tasks.  
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
    
    
  4. Next, apply Task.WhenAll to the collection of tasks, downloadTasksTask.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    In the following example, the await expression awaits the completion of the single task that WhenAll returns. When complete, the awaitexpression evaluates to an array of integers, where each integer is the length of a downloaded website. Add the following code to SumPageSizesAsync, just after the code that you added in the previous step.

    // Await the completion of all the running tasks.  
    int[] lengths = await Task.WhenAll(downloadTasks);  
    
    //// The previous line is equivalent to the following two statements.  
    //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);  
    //int[] lengths = await whenAllTask;  
    
    
  5. Finally, use the Sum method to get the sum of the lengths of all the websites. Add the following line to SumPageSizesAsync.

    Dim total = lengths.Sum()  
    
    

To test the Task.WhenAll solutions

The following code shows the extensions to the project that uses the GetURLContentsAsync method to download content from the web.

// Add the following using directives, and add a reference for System.Net.Http.  
using System.Net.Http;  
using System.IO;  
using System.Net;  
  
namespace AsyncExampleWPF_WhenAll  
{  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            resultsTextBox.Clear();  
  
            // Two-step async call.  
            Task sumTask = SumPageSizesAsync();  
            await sumTask;  
  
            // One-step async call.  
            //await SumPageSizesAsync();  
  
            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";  
        }  
  
        private async Task SumPageSizesAsync()  
        {  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            // Create a query.   
            IEnumerable<Task<int>> downloadTasksQuery =   
                from url in urlList select ProcessURLAsync(url);  
  
            // Use ToArray to execute the query and start the download tasks.  
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
  
            // You can do other work here before awaiting.  
  
            // Await the completion of all the running tasks.  
            int[] lengths = await Task.WhenAll(downloadTasks);  
  
            //// The previous line is equivalent to the following two statements.  
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);  
            //int[] lengths = await whenAllTask;  
  
            int total = lengths.Sum();  
  
            //var total = 0;  
            //foreach (var url in urlList)  
            //{  
            //    byte[] urlContents = await GetURLContentsAsync(url);  
  
            //    // The previous line abbreviates the following two assignment statements.  
            //    // GetURLContentsAsync returns a Task<T>. At completion, the task  
            //    // produces a byte array.  
            //    //Task<byte[]> getContentsTask = GetURLContentsAsync(url);  
            //    //byte[] urlContents = await getContentsTask;  
  
            //    DisplayResults(url, urlContents);  
  
            //    // Update the total.            
            //    total += urlContents.Length;  
            //}  
  
            // Display the total count for all of the websites.  
            resultsTextBox.Text +=  
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
  
        // The actions from the foreach loop are moved to this async method.  
        private async Task<int> ProcessURLAsync(string url)  
        {  
            var byteArray = await GetURLContentsAsync(url);  
            DisplayResults(url, byteArray);  
            return byteArray.Length;  
        }  
  
        private async Task<byte[]> GetURLContentsAsync(string url)  
        {  
            // The downloaded resource ends up in the variable named content.  
            var content = new MemoryStream();  
  
            // Initialize an HttpWebRequest for the current URL.  
            var webReq = (HttpWebRequest)WebRequest.Create(url);  
  
            // Send the request to the Internet resource and wait for  
            // the response.  
            using (WebResponse response = await webReq.GetResponseAsync())  
            {  
                // Get the data stream that is associated with the specified url.  
                using (Stream responseStream = response.GetResponseStream())  
                {  
                    await responseStream.CopyToAsync(content);  
                }  
            }  
  
            // Return the result as a byte array.  
            return content.ToArray();  
  
        }  
  
        private void DisplayResults(string url, byte[] content)  
        {  
            // Display the length of each website. The string format   
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;  
            // Strip off the "http://".  
            var displayURL = url.Replace("http://", "");  
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
  
        }  
    }  
}  

The following code shows the extensions to the project that uses method HttpClient.GetByteArrayAsync to download content from the web.

// Add the following using directives, and add a reference for System.Net.Http.  
using System.Net.Http;  
using System.IO;  
using System.Net;  
  
namespace AsyncExampleWPF_HttpClient_WhenAll  
{  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            resultsTextBox.Clear();  
  
            // One-step async call.  
            await SumPageSizesAsync();  
  
            // Two-step async call.  
            //Task sumTask = SumPageSizesAsync();  
            //await sumTask;  
  
            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";  
        }  
  
        private async Task SumPageSizesAsync()  
        {  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            // Declare an HttpClient object and increase the buffer size. The  
            // default buffer size is 65,536.  
            HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 };  
  
            // Create a query.  
            IEnumerable<Task<int>> downloadTasksQuery =   
                from url in urlList select ProcessURL(url, client);  
  
            // Use ToArray to execute the query and start the download tasks.  
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
  
            // You can do other work here before awaiting.  
  
            // Await the completion of all the running tasks.  
            int[] lengths = await Task.WhenAll(downloadTasks);  
  
            //// The previous line is equivalent to the following two statements.  
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);  
            //int[] lengths = await whenAllTask;  
  
            int total = lengths.Sum();  
  
            //var total = 0;  
            //foreach (var url in urlList)  
            //{  
            //    // GetByteArrayAsync returns a Task<T>. At completion, the task  
            //    // produces a byte array.  
            //    byte[] urlContent = await client.GetByteArrayAsync(url);  
  
            //    // The previous line abbreviates the following two assignment  
            //    // statements.  
            //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url);  
            //    byte[] urlContent = await getContentTask;  
  
            //    DisplayResults(url, urlContent);  
  
            //    // Update the total.  
            //    total += urlContent.Length;  
            //}  
  
            // Display the total count for all of the web addresses.  
            resultsTextBox.Text +=  
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
  
        // The actions from the foreach loop are moved to this async method.  
        async Task<int> ProcessURL(string url, HttpClient client)  
        {  
            byte[] byteArray = await client.GetByteArrayAsync(url);  
            DisplayResults(url, byteArray);  
            return byteArray.Length;  
        }  
  
        private void DisplayResults(string url, byte[] content)  
        {  
            // Display the length of each web site. The string format   
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;  
            // Strip off the "http://".  
            var displayURL = url.Replace("http://", "");  
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
        }  
    }  
}






How to: Make Multiple Web Requests in Parallel by Using async and await (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

In an async method, tasks are started when they’re created. The await operator is applied to the task at the point in the method where processing can’t continue until the task finishes. Often a task is awaited as soon as it’s created, as the following example shows.

var result = await someWebAccessMethodAsync(url);  

However, you can separate creating the task from awaiting the task if your program has other work to accomplish that doesn’t depend on the completion of the task.

// The following line creates and starts the task.  
var myTask = someWebAccessMethodAsync(url);  
  
// While the task is running, you can do other work that doesn't depend  
// on the results of the task.  
// . . . . .  
  
// The application of await suspends the rest of this method until the task is complete.  
var result = await myTask;  
  

Between starting a task and awaiting it, you can start other tasks. The additional tasks implicitly run in parallel, but no additional threads are created.

The following program starts three asynchronous web downloads and then awaits them in the order in which they’re called. Notice, when you run the program, that the tasks don’t always finish in the order in which they’re created and awaited. They start to run when they’re created, and one or more of the tasks might finish before the method reaches the await expressions.

System_CAPS_ICON_note.jpg Note

To complete this project, you must have Visual Studio 2012 or higher and the .NET Framework 4.5 or higher installed on your computer.

For another example that starts multiple tasks at the same time, see How to: Extend the async Walkthrough by Using Task.WhenAll (C#).

You can download the code for this example from Developer Code Samples.

To set up the project

  1. To set up a WPF application, complete the following steps. You can find detailed instructions for these steps in Walkthrough: Accessing the Web by Using async and await (C#).

    • Create a WPF application that contains a text box and a button. Name the button startButton, and name the text box resultsTextBox.

    • Add a reference for System.Net.Http.

    • In the MainWindow.xaml.cs file, add a using directive for System.Net.Http.

To add the code

  1. In the design window, MainWindow.xaml, double-click the button to create the startButton_Click event handler in MainWindow.xaml.cs.

  2. Copy the following code, and paste it into the body of startButton_Click in MainWindow.xaml.cs.

    resultsTextBox.Clear();  
    await CreateMultipleTasksAsync();  
    resultsTextBox.Text += "\r\n\r\nControl returned to startButton_Click.\r\n";  
    
    

    The code calls an asynchronous method, CreateMultipleTasksAsync, which drives the application.

  3. Add the following support methods to the project:

    • ProcessURLAsync uses an HttpClient method to download the contents of a website as a byte array. The support method, ProcessURLAsync then displays and returns the length of the array.

    • DisplayResults displays the number of bytes in the byte array for each URL. This display shows when each task has finished downloading.

    Copy the following methods, and paste them after the startButton_Click event handler in MainWindow.xaml.cs.

    async Task<int> ProcessURLAsync(string url, HttpClient client)  
    {  
        var byteArray = await client.GetByteArrayAsync(url);  
        DisplayResults(url, byteArray);  
        return byteArray.Length;  
    }  
    
    private void DisplayResults(string url, byte[] content)  
    {  
        // Display the length of each website. The string format   
        // is designed to be used with a monospaced font, such as  
        // Lucida Console or Global Monospace.  
        var bytes = content.Length;  
        // Strip off the "http://".  
        var displayURL = url.Replace("http://", "");  
        resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
    }  
    
    
  4. Finally, define method CreateMultipleTasksAsync, which performs the following steps.

    • The method declares an HttpClient object,which you need to access method GetByteArrayAsync in ProcessURLAsync.

    • The method creates and starts three tasks of type Task<TResult>, where TResult is an integer. As each task finishes, DisplayResultsdisplays the task's URL and the length of the downloaded contents. Because the tasks are running asynchronously, the order in which the results appear might differ from the order in which they were declared.

    • The method awaits the completion of each task. Each await operator suspends execution of CreateMultipleTasksAsync until the awaited task is finished. The operator also retrieves the return value from the call to ProcessURLAsync from each completed task.

    • When the tasks have been completed and the integer values have been retrieved, the method sums the lengths of the websites and displays the result.

    Copy the following method, and paste it into your solution.

    private async Task CreateMultipleTasksAsync()  
    {  
        // Declare an HttpClient object, and increase the buffer size. The  
        // default buffer size is 65,536.  
        HttpClient client =  
            new HttpClient() { MaxResponseContentBufferSize = 1000000 };  
    
        // Create and start the tasks. As each task finishes, DisplayResults   
        // displays its length.  
        Task<int> download1 =   
            ProcessURLAsync("http://msdn.microsoft.com", client);  
        Task<int> download2 =   
            ProcessURLAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx", client);  
        Task<int> download3 =   
            ProcessURLAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx", client);  
    
        // Await each task.  
        int length1 = await download1;  
        int length2 = await download2;  
        int length3 = await download3;  
    
        int total = length1 + length2 + length3;  
    
        // Display the total count for the downloaded websites.  
        resultsTextBox.Text +=  
            string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
    }  
    
    
  5. Choose the F5 key to run the program, and then choose the Start button.

    Run the program several times to verify that the three tasks don’t always finish in the same order and that the order in which they finish isn't necessarily the order in which they’re created and awaited.

The following code contains the full example.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add the following using directive, and add a reference for System.Net.Http.  
using System.Net.Http;  
  
namespace AsyncExample_MultipleTasks  
{  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            resultsTextBox.Clear();  
            await CreateMultipleTasksAsync();  
            resultsTextBox.Text += "\r\n\r\nControl returned to startButton_Click.\r\n";  
        }  
  
        private async Task CreateMultipleTasksAsync()  
        {  
            // Declare an HttpClient object, and increase the buffer size. The  
            // default buffer size is 65,536.  
            HttpClient client =  
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };  
  
            // Create and start the tasks. As each task finishes, DisplayResults   
            // displays its length.  
            Task<int> download1 =   
                ProcessURLAsync("http://msdn.microsoft.com", client);  
            Task<int> download2 =   
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx", client);  
            Task<int> download3 =   
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx", client);  
  
            // Await each task.  
            int length1 = await download1;  
            int length2 = await download2;  
            int length3 = await download3;  
  
            int total = length1 + length2 + length3;  
  
            // Display the total count for the downloaded websites.  
            resultsTextBox.Text +=  
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);  
        }  
  
        async Task<int> ProcessURLAsync(string url, HttpClient client)  
        {  
            var byteArray = await client.GetByteArrayAsync(url);  
            DisplayResults(url, byteArray);  
            return byteArray.Length;  
        }  
  
        private void DisplayResults(string url, byte[] content)  
        {  
            // Display the length of each website. The string format   
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;  
            // Strip off the "http://".  
            var displayURL = url.Replace("http://", "");  
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);  
        }  
    }  
}






Async Return Types (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

Async methods have three possible return types: Task<TResult>Task, and void. In Visual Basic, the void return type is written as a Sub procedure. For more information about async methods, see Asynchronous Programming with async and await (C#).

Each return type is examined in one of the following sections, and you can find a full example that uses all three types at the end of the topic.

System_CAPS_ICON_note.jpg Note

To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

The Task<TResult> return type is used for an async method that contains areturn (C#) statement in which the operand has type TResult.

In the following example, the TaskOfT_MethodAsync async method contains a return statement that returns an integer. Therefore, the method declaration must specify a return type of Task<int>.

// TASK<T> EXAMPLE  
async Task<int> TaskOfT_MethodAsync()  
{  
    // The body of the method is expected to contain an awaited asynchronous  
    // call.  
    // Task.FromResult is a placeholder for actual work that returns a string.  
    var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());  
  
    // The method then can process the result in some way.  
    int leisureHours;  
    if (today.First() == 'S')  
        leisureHours = 16;  
    else  
        leisureHours = 5;  
  
    // Because the return statement specifies an operand of type int, the  
    // method must have a return type of Task<int>.  
    return leisureHours;  
}  

When TaskOfT_MethodAsync is called from within an await expression, the await expression retrieves the integer value (the value of leisureHours) that's stored in the task that's returned by TaskOfT_MethodAsync. For more information about await expressions, see await.

The following code calls and awaits method TaskOfT_MethodAsync. The result is assigned to the result1 variable.

// Call and await the Task<T>-returning async method in the same statement.  
int result1 = await TaskOfT_MethodAsync();  

You can better understand how this happens by separating the call to TaskOfT_MethodAsync from the application of await, as the following code shows. A call to method TaskOfT_MethodAsync that isn't immediately awaited returns a Task<int>, as you would expect from the declaration of the method. The task is assigned to the integerTask variable in the example. Because integerTask is a Task<TResult>, it contains a Result property of type TResult. In this case, TResult represents an integer type. When await is applied to integerTask, the await expression evaluates to the contents of the Result property of integerTask. The value is assigned to the result2 variable.

System_CAPS_ICON_warning.jpg Warning

The Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using await instead of accessing the property directly.

// Call and await in separate statements.  
Task<int> integerTask = TaskOfT_MethodAsync();  
  
// You can do other work that does not rely on integerTask before awaiting.  
textBox1.Text += String.Format("Application can continue working while the Task<T> runs. . . . \r\n");  
  
int result2 = await integerTask;  

The display statements in the following code verify that the values of the result1 variable, the result2 variable, and the Result property are the same. Remember that the Result property is a blocking property and shouldn't be accessed before its task has been awaited.

// Display the values of the result1 variable, the result2 variable, and  
// the integerTask.Result property.  
textBox1.Text += String.Format("\r\nValue of result1 variable:   {0}\r\n", result1);  
textBox1.Text += String.Format("Value of result2 variable:   {0}\r\n", result2);  
textBox1.Text += String.Format("Value of integerTask.Result: {0}\r\n", integerTask.Result);  

Async methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of Task. Such methods would be void-returning methods if they were written to run synchronously. If you use a Task return type for an async method, a calling method can use an await operator to suspend the caller's completion until the called async method has finished.

In the following example, async method Task_MethodAsync doesn't contain a return statement. Therefore, you specify a return type of Task for the method, which enables Task_MethodAsync to be awaited. The definition of the Task type doesn't include a Result property to store a return value.

// TASK EXAMPLE  
async Task Task_MethodAsync()  
{  
    // The body of an async method is expected to contain an awaited   
    // asynchronous call.  
    // Task.Delay is a placeholder for actual work.  
    await Task.Delay(2000);  
    // Task.Delay delays the following line by two seconds.  
    textBox1.Text += String.Format("\r\nSorry for the delay. . . .\r\n");  
  
    // This method has no return statement, so its return type is Task.    
}  

Task_MethodAsync is called and awaited by using an await statement instead of an await expression, similar to the calling statement for a synchronous void-returning method. The application of an await operator in this case doesn't produce a value.

The following code calls and awaits method Task_MethodAsync.

// Call and await the Task-returning async method in the same statement.  
await Task_MethodAsync();  

As in the previous Task<TResult> example, you can separate the call to Task_MethodAsync from the application of an await operator, as the following code shows. However, remember that a Task doesn't have a Result property, and that no value is produced when an await operator is applied to a Task.

The following code separates calling Task_MethodAsync from awaiting the task that Task_MethodAsync returns.

// Call and await in separate statements.  
Task simpleTask = Task_MethodAsync();  
  
// You can do other work that does not rely on simpleTask before awaiting.  
textBox1.Text += String.Format("\r\nApplication can continue working while the Task runs. . . .\r\n");  
  
await simpleTask;  

The primary use of the void return type is in event handlers, where a void return type is required. A void return also can be used to override void-returning methods or for methods that perform activities that can be categorized as "fire and forget." However, you should return a Task wherever possible, because a void-returning async method can't be awaited. Any caller of such a method must be able to continue to completion without waiting for the called async method to finish, and the caller must be independent of any values or exceptions that the async method generates.

The caller of a void-returning async method can't catch exceptions that are thrown from the method, and such unhandled exceptions are likely to cause your application to fail. If an exception occurs in an async method that returns a Task or Task<TResult>, the exception is stored in the returned task, and rethrown when the task is awaited. Therefore, make sure that any async method that can produce an exception has a return type of Task or Task<TResult> and that calls to the method are awaited.

For more information about how to catch exceptions in async methods, see try-catch .

The following code defines an async event handler.

// VOID EXAMPLE  
private async void button1_Click(object sender, RoutedEventArgs e)  
{  
    textBox1.Clear();  
  
    // Start the process and await its completion. DriverAsync is a   
    // Task-returning async method.  
    await DriverAsync();  
  
    // Say goodbye.  
    textBox1.Text += "\r\nAll done, exiting button-click event handler.";  
}  

The following Windows Presentation Foundation (WPF) project contains the code examples from this topic.

To run the project, perform the following steps:

  1. Start Visual Studio.

  2. On the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  3. In the InstalledTemplates category, choose Visual C#, and then choose Windows. Choose WPF Application from the list of project types.

  4. Enter AsyncReturnTypes as the name of the project, and then choose the OK button.

    The new project appears in Solution Explorer.

  5. In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    If the tab is not visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose Open.

  6. In the XAML window of MainWindow.xaml, replace the code with the following code.

    <Window x:Class="AsyncReturnTypes.MainWindow"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            Title="MainWindow" Height="350" Width="525">  
        <Grid>  
            <Button x:Name="button1" Content="Start" HorizontalAlignment="Left" Margin="214,28,0,0" VerticalAlignment="Top" Width="75" HorizontalContentAlignment="Center" FontWeight="Bold" FontFamily="Aharoni" Click="button1_Click"/>  
            <TextBox x:Name="textBox1" Margin="0,80,0,0" TextWrapping="Wrap" FontFamily="Lucida Console"/>  
    
        </Grid>  
    </Window>  
    
    
    

    A simple window that contains a text box and a button appears in the Design window of MainWindow.xaml.

  7. In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  8. Replace the code in MainWindow.xaml.cs with the following code.

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Threading.Tasks;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Navigation;  
    using System.Windows.Shapes;  
    
    namespace AsyncReturnTypes  
    {  
        public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
            }  
    
            // VOID EXAMPLE  
            private async void button1_Click(object sender, RoutedEventArgs e)  
            {  
                textBox1.Clear();  
    
                // Start the process and await its completion. DriverAsync is a   
                // Task-returning async method.  
                await DriverAsync();  
    
                // Say goodbye.  
                textBox1.Text += "\r\nAll done, exiting button-click event handler.";  
            }  
    
            async Task DriverAsync()  
            {  
                // Task<T>   
                // Call and await the Task<T>-returning async method in the same statement.  
                int result1 = await TaskOfT_MethodAsync();  
    
                // Call and await in separate statements.  
                Task<int> integerTask = TaskOfT_MethodAsync();  
    
                // You can do other work that does not rely on integerTask before awaiting.  
                textBox1.Text += String.Format("Application can continue working while the Task<T> runs. . . . \r\n");  
    
                int result2 = await integerTask;  
    
                // Display the values of the result1 variable, the result2 variable, and  
                // the integerTask.Result property.  
                textBox1.Text += String.Format("\r\nValue of result1 variable:   {0}\r\n", result1);  
                textBox1.Text += String.Format("Value of result2 variable:   {0}\r\n", result2);  
                textBox1.Text += String.Format("Value of integerTask.Result: {0}\r\n", integerTask.Result);  
    
                // Task  
                // Call and await the Task-returning async method in the same statement.  
                await Task_MethodAsync();  
    
                // Call and await in separate statements.  
                Task simpleTask = Task_MethodAsync();  
    
                // You can do other work that does not rely on simpleTask before awaiting.  
                textBox1.Text += String.Format("\r\nApplication can continue working while the Task runs. . . .\r\n");  
    
                await simpleTask;  
            }  
    
            // TASK<T> EXAMPLE  
            async Task<int> TaskOfT_MethodAsync()  
            {  
                // The body of the method is expected to contain an awaited asynchronous  
                // call.  
                // Task.FromResult is a placeholder for actual work that returns a string.  
                var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());  
    
                // The method then can process the result in some way.  
                int leisureHours;  
                if (today.First() == 'S')  
                    leisureHours = 16;  
                else  
                    leisureHours = 5;  
    
                // Because the return statement specifies an operand of type int, the  
                // method must have a return type of Task<int>.  
                return leisureHours;  
            }  
    
            // TASK EXAMPLE  
            async Task Task_MethodAsync()  
            {  
                // The body of an async method is expected to contain an awaited   
                // asynchronous call.  
                // Task.Delay is a placeholder for actual work.  
                await Task.Delay(2000);  
                // Task.Delay delays the following line by two seconds.  
                textBox1.Text += String.Format("\r\nSorry for the delay. . . .\r\n");  
    
                // This method has no return statement, so its return type is Task.    
            }  
        }  
    }  
    
    
  9. Choose the F5 key to run the program, and then choose the Start button.

    The following output should appear.

    Application can continue working while the Task<T> runs. . . .   
    
    Value of result1 variable:   5  
    Value of result2 variable:   5  
    Value of integerTask.Result: 5  
    
    Sorry for the delay. . . .  
    
    Application can continue working while the Task runs. . . .  
    
    Sorry for the delay. . . .  
    
    All done, exiting button-click event handler.






Control Flow in Async Programs (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can write and maintain asynchronous programs more easily by using the async and await keywords. However, the results might surprise you if you don't understand how your program operates. This topic traces the flow of control through a simple async program to show you when control moves from one method to another and what information is transferred each time.

System_CAPS_ICON_note.jpg Note

The async and await keywords were introduced in Visual Studio 2012.

In general, you mark methods that contain asynchronous code with the async (C#) modifier. In a method that's marked with an async modifier, you can use an await (C#) operator to specify where the method pauses to wait for a called asynchronous process to complete. For more information, see Asynchronous Programming with async and await (C#).

The following example uses async methods to download the contents of a specified website as a string and to display the length of the string. The example contains the following two methods.

  • startButton_Click, which calls AccessTheWebAsync and displays the result.

  • AccessTheWebAsync, which downloads the contents of a website as a string and returns the length of the string. AccessTheWebAsync uses an asynchronous HttpClient method, GetStringAsync(String), to download the contents.

Numbered display lines appear at strategic points throughout the program to help you understand how the program runs and to explain what happens at each point that is marked. The display lines are labeled "ONE" through "SIX." The labels represent the order in which the program reaches these lines of code.

The following code shows an outline of the program.

  
public partial class MainWindow : Window  
{  
    // . . .  
    private async void startButton_Click(object sender, RoutedEventArgs e)  
    {  
        // ONE  
        Task<int> getLengthTask = AccessTheWebAsync();  
  
        // FOUR  
        int contentLength = await getLengthTask;  
  
        // SIX  
        resultsTextBox.Text +=  
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
    }  
  
    async Task<int> AccessTheWebAsync()  
    {  
        // TWO  
        HttpClient client = new HttpClient();  
        Task<string> getStringTask =  
            client.GetStringAsync("http://msdn.microsoft.com");  
  
        // THREE                   
        string urlContents = await getStringTask;  
  
        // FIVE  
        return urlContents.Length;  
    }  
}  
  

Each of the labeled locations, "ONE" through "SIX," displays information about the current state of the program. The following output is produced.

  
ONE:   Entering startButton_Click.  
           Calling AccessTheWebAsync.  
  
TWO:   Entering AccessTheWebAsync.  
           Calling HttpClient.GetStringAsync.  
  
THREE: Back in AccessTheWebAsync.  
           Task getStringTask is started.  
           About to await getStringTask & return a Task<int> to startButton_Click.  
  
FOUR:  Back in startButton_Click.  
           Task getLengthTask is started.  
           About to await getLengthTask -- no caller to return to.  
  
FIVE:  Back in AccessTheWebAsync.  
           Task getStringTask is complete.  
           Processing the return statement.  
           Exiting from AccessTheWebAsync.  
  
SIX:   Back in startButton_Click.  
           Task getLengthTask is finished.  
           Result from AccessTheWebAsync is stored in contentLength.  
           About to display contentLength and exit.  
  
Length of the downloaded string: 33946.  

You can download the code that this topic uses from MSDN, or you can build it yourself.

System_CAPS_ICON_note.jpg Note

To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Download the Program

You can download the application for this topic from Async Sample: Control Flow in Async Programs. The following steps open and run the program.

  1. Unzip the downloaded file, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. Navigate to the folder that holds the unzipped sample code, open the solution (.sln) file, and then choose the F5 key to build and run the project.

Build the Program Yourself

The following Windows Presentation Foundation (WPF) project contains the code example for this topic.

To run the project, perform the following steps:

  1. Start Visual Studio.

  2. On the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  3. In the Installed Templates pane, choose Visual C#, and then choose WPF Application from the list of project types.

  4. Enter AsyncTracer as the name of the project, and then choose the OK button.

    The new project appears in Solution Explorer.

  5. In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  6. In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow"  
            Title="Control Flow Trace" Height="350" Width="592">  
        <Grid>  
            <Button x:Name="startButton" Content="Start  
    " HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24"  Click="startButton_Click" d:LayoutOverrides="GridBox"/>  
            <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/>  
        </Grid>  
    </Window>  
    
    
    

    A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  7. Add a reference for System.Net.Http.

  8. In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  9. In MainWindow.xaml.cs, replace the code with the following code.

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Threading.Tasks;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Navigation;  
    using System.Windows.Shapes;  
    
    // Add a using directive and a reference for System.Net.Http;  
    using System.Net.Http;  
    
    namespace AsyncTracer  
    {  
        public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
            }  
    
            private async void startButton_Click(object sender, RoutedEventArgs e)  
            {  
                // The display lines in the example lead you through the control shifts.  
                resultsTextBox.Text += "ONE:   Entering startButton_Click.\r\n" +  
                    "           Calling AccessTheWebAsync.\r\n";  
    
                Task<int> getLengthTask = AccessTheWebAsync();  
    
                resultsTextBox.Text += "\r\nFOUR:  Back in startButton_Click.\r\n" +  
                    "           Task getLengthTask is started.\r\n" +  
                    "           About to await getLengthTask -- no caller to return to.\r\n";  
    
                int contentLength = await getLengthTask;  
    
                resultsTextBox.Text += "\r\nSIX:   Back in startButton_Click.\r\n" +  
                    "           Task getLengthTask is finished.\r\n" +  
                    "           Result from AccessTheWebAsync is stored in contentLength.\r\n" +  
                    "           About to display contentLength and exit.\r\n";  
    
                resultsTextBox.Text +=  
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
            }  
    
            async Task<int> AccessTheWebAsync()  
            {  
                resultsTextBox.Text += "\r\nTWO:   Entering AccessTheWebAsync.";  
    
                // Declare an HttpClient object.  
                HttpClient client = new HttpClient();  
    
                resultsTextBox.Text += "\r\n           Calling HttpClient.GetStringAsync.\r\n";  
    
                // GetStringAsync returns a Task<string>.   
                Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
    
                resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" +  
                    "           Task getStringTask is started.";  
    
                // AccessTheWebAsync can continue to work until getStringTask is awaited.  
    
                resultsTextBox.Text +=  
                    "\r\n           About to await getStringTask and return a Task<int> to startButton_Click.\r\n";  
    
                // Retrieve the website contents when task is complete.  
                string urlContents = await getStringTask;  
    
                resultsTextBox.Text += "\r\nFIVE:  Back in AccessTheWebAsync." +  
                    "\r\n           Task getStringTask is complete." +  
                    "\r\n           Processing the return statement." +  
                    "\r\n           Exiting from AccessTheWebAsync.\r\n";  
    
                return urlContents.Length;  
            }  
        }  
    }  
    
    
  10. Choose the F5 key to run the program, and then choose the Start button.

    The following output should appear.

    ONE:   Entering startButton_Click.  
               Calling AccessTheWebAsync.  
    
    TWO:   Entering AccessTheWebAsync.  
               Calling HttpClient.GetStringAsync.  
    
    THREE: Back in AccessTheWebAsync.  
               Task getStringTask is started.  
               About to await getStringTask & return a Task<int> to startButton_Click.  
    
    FOUR:  Back in startButton_Click.  
               Task getLengthTask is started.  
               About to await getLengthTask -- no caller to return to.  
    
    FIVE:  Back in AccessTheWebAsync.  
               Task getStringTask is complete.  
               Processing the return statement.  
               Exiting from AccessTheWebAsync.  
    
    SIX:   Back in startButton_Click.  
               Task getLengthTask is finished.  
               Result from AccessTheWebAsync is stored in contentLength.  
               About to display contentLength and exit.  
    
    Length of the downloaded string: 33946.  
    
    

Steps ONE and TWO

The first two display lines trace the path as startButton_Click calls AccessTheWebAsync, and AccessTheWebAsync calls the asynchronous HttpClient method GetStringAsync(String). The following image outlines the calls from method to method.

Steps ONE and TWO

The return type of both AccessTheWebAsync and client.GetStringAsync is Task<TResult>. For AccessTheWebAsync, TResult is an integer. For GetStringAsync, TResult is a string. For more information about async method return types, see Async Return Types (C#).

A task-returning async method returns a task instance when control shifts back to the caller. Control returns from an async method to its caller either when an await operator is encountered in the called method or when the called method ends. The display lines that are labeled "THREE" through "SIX" trace this part of the process.

Step THREE

In AccessTheWebAsync, the asynchronous method GetStringAsync(String) is called to download the contents of the target webpage. Control returns from client.GetStringAsync to AccessTheWebAsync when client.GetStringAsync returns.

The client.GetStringAsync method returns a task of string that’s assigned to the getStringTask variable in AccessTheWebAsync. The following line in the example program shows the call to client.GetStringAsync and the assignment.

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  

You can think of the task as a promise by client.GetStringAsync to produce an actual string eventually. In the meantime, if AccessTheWebAsync has work to do that doesn't depend on the promised string from client.GetStringAsync, that work can continue while client.GetStringAsync waits. In the example, the following lines of output, which are labeled "THREE,” represent the opportunity to do independent work

  
THREE: Back in AccessTheWebAsync.  
           Task getStringTask is started.  
           About to await getStringTask & return a Task<int> to startButton_Click.  
  

The following statement suspends progress in AccessTheWebAsync when getStringTask is awaited.

string urlContents = await getStringTask;  

The following image shows the flow of control from client.GetStringAsync to the assignment to getStringTask and from the creation of getStringTask to the application of an await operator.

Step THREE

The await expression suspends AccessTheWebAsync until client.GetStringAsync returns. In the meantime, control returns to the caller of AccessTheWebAsyncstartButton_Click.

System_CAPS_ICON_note.jpg Note

Typically, you await the call to an asynchronous method immediately. For example, the following assignment could replace the previous code that creates and then awaits getStringTaskstring urlContents = await client.GetStringAsync("http://msdn.microsoft.com");

In this topic, the await operator is applied later to accommodate the output lines that mark the flow of control through the program.

Step FOUR

The declared return type of AccessTheWebAsync is Task<int>. Therefore, when AccessTheWebAsync is suspended, it returns a task of integer to startButton_Click. You should understand that the returned task isn’t getStringTask. The returned task is a new task of integer that represents what remains to be done in the suspended method, AccessTheWebAsync. The task is a promise from AccessTheWebAsync to produce an integer when the task is complete.

The following statement assigns this task to the getLengthTask variable.

Task<int> getLengthTask = AccessTheWebAsync();  

As in AccessTheWebAsyncstartButton_Click can continue with work that doesn’t depend on the results of the asynchronous task (getLengthTask) until the task is awaited. The following output lines represent that work.

  
FOUR:  Back in startButton_Click.  
           Task getLengthTask is started.  
           About to await getLengthTask -- no caller to return to.  
  

Progress in startButton_Click is suspended when getLengthTask is awaited. The following assignment statement suspends startButton_Click until AccessTheWebAsync is complete.

int contentLength = await getLengthTask;  

In the following illustration, the arrows show the flow of control from the await expression in AccessTheWebAsync to the assignment of a value to getLengthTask, followed by normal processing in startButton_Click until getLengthTask is awaited.

Step FOUR

Step FIVE

When client.GetStringAsync signals that it’s complete, processing in AccessTheWebAsync is released from suspension and can continue past the await statement. The following lines of output represent the resumption of processing.

  
FIVE:  Back in AccessTheWebAsync.  
           Task getStringTask is complete.  
           Processing the return statement.  
           Exiting from AccessTheWebAsync.  
  

The operand of the return statement, urlContents.Length, is stored in the task that AccessTheWebAsync returns. The await expression retrieves that value from getLengthTask in startButton_Click.

The following image shows the transfer of control after client.GetStringAsync (and getStringTask) are complete.

Step FIVE

AccessTheWebAsync runs to completion, and control returns to startButton_Click, which is awaiting the completion.

Step SIX

When AccessTheWebAsync signals that it’s complete, processing can continue past the await statement in startButton_Async. In fact, the program has nothing more to do.

The following lines of output represent the resumption of processing in startButton_Async:

  
SIX:   Back in startButton_Click.  
           Task getLengthTask is finished.  
           Result from AccessTheWebAsync is stored in contentLength.  
           About to display contentLength and exit.  
  

The await expression retrieves from getLengthTask the integer value that’s the operand of the return statement in AccessTheWebAsync. The following statement assigns that value to the contentLength variable.

int contentLength = await getLengthTask;  

The following image shows the return of control from AccessTheWebAsync to startButton_Click.

Step SIX






Fine-Tuning Your Async Application (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can add precision and flexibility to your async applications by using the methods and properties that the Task type makes available. The topics in this section show examples that use CancellationToken and important Task methods such as Task.WhenAll and Task.WhenAny.

By using WhenAny and WhenAll, you can more easily start multiple tasks and await their completion by monitoring a single task.

This section includes the following examples.

System_CAPS_ICON_note.jpg Note

To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

The projects create a UI that contains a button that starts the process and a button that cancels it, as the following image shows. The buttons are named startButton and cancelButton.

WPF window with Cancel button

You can download the complete Windows Presentation Foundation (WPF) projects from Async Sample: Fine Tuning Your Application.






Cancel an Async Task or a List of Tasks (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can set up a button that you can use to cancel an async application if you don't want to wait for it to finish. By following the examples in this topic, you can add a cancellation button to an application that downloads the contents of one website or a list of websites.

The examples use the UI that Fine-Tuning Your Async Application (C#) describes.

System_CAPS_ICON_note.jpg Note

To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

The first example associates the Cancel button with a single download task. If you choose the button while the application is downloading content, the download is canceled.

Downloading the Example

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS.

  4. In Solution Explorer, open the shortcut menu for the CancelATask project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

If you don't want to download the project, you can review the MainWindow.xaml.cs files at the end of this topic.

Building the Example

The following changes add a Cancel button to an application that downloads a website. If you don't want to download or build the example, you can review the final product in the "Complete Examples" section at the end of this topic. Asterisks mark the changes in the code.

To build the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose StarterCode as the StartUp Project instead of CancelATask.

Then add the following changes to the MainWindow.xaml.cs file of that project.

  1. Declare a CancellationTokenSource variable, cts, that’s in scope for all methods that access it.

    public partial class MainWindow : Window  
    {  
        // ***Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
    
    
  2. Add the following event handler for the Cancel button. The event handler uses the CancellationTokenSource.Cancel method to notify cts when the user requests cancellation.

    // ***Add an event handler for the Cancel button.  
    private void cancelButton_Click(object sender, RoutedEventArgs e)  
    {  
        if (cts != null)  
        {  
            cts.Cancel();  
        }  
    }  
    
    
  3. Make the following changes in the event handler for the Start button, startButton_Click.

    • Instantiate the CancellationTokenSourcects.

      // ***Instantiate the CancellationTokenSource.  
      cts = new CancellationTokenSource();  
      
      
    • In the call to AccessTheWebAsync, which downloads the contents of a specified website, send the CancellationTokenSource.Token property of cts as an argument. The Token property propagates the message if cancellation is requested. Add a catch block that displays a message if the user chooses to cancel the download operation. The following code shows the changes.

      try  
      {  
          // ***Send a token to carry the message if cancellation is requested.  
          int contentLength = await AccessTheWebAsync(cts.Token);  
          resultsTextBox.Text +=  
              String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
      }  
      // *** If cancellation is requested, an OperationCanceledException results.  
      catch (OperationCanceledException)  
      {  
          resultsTextBox.Text += "\r\nDownload canceled.\r\n";  
      }  
      catch (Exception)  
      {  
          resultsTextBox.Text += "\r\nDownload failed.\r\n";  
      }  
      
      
  4. In AccessTheWebAsync, use the HttpClient.GetAsync(String, CancellationToken) overload of the GetAsync method in the HttpClient type to download the contents of a website. Pass ct, the CancellationToken parameter of AccessTheWebAsync, as the second argument. The token carries the message if the user chooses the Cancel button.

    The following code shows the changes in AccessTheWebAsync.

    // ***Provide a parameter for the CancellationToken.  
    async Task<int> AccessTheWebAsync(CancellationToken ct)  
    {  
        HttpClient client = new HttpClient();  
    
        resultsTextBox.Text +=  
            String.Format("\r\nReady to download.\r\n");  
    
        // You might need to slow things down to have a chance to cancel.  
        await Task.Delay(250);  
    
        // GetAsync returns a Task<HttpResponseMessage>.   
        // ***The ct argument carries the message if the Cancel button is chosen.  
        HttpResponseMessage response = await client.GetAsync("http://msdn.microsoft.com/en-us/library/dd470362.aspx", ct);  
    
        // Retrieve the website contents from the HttpResponseMessage.  
        byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
    
        // The result of the method is the length of the downloaded website.  
        return urlContents.Length;  
    }  
    
    
  5. If you don’t cancel the program, it produces the following output.

    Ready to download.  
    Length of the downloaded string: 158125.  
    
    

    If you choose the Cancel button before the program finishes downloading the content, the program produces the following output.

    Ready to download.  
    Download canceled.  
    
    

You can extend the previous example to cancel many tasks by associating the same CancellationTokenSource instance with each task. If you choose the Cancel button, you cancel all tasks that aren’t yet complete.

Downloading the Example

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS.

  4. In Solution Explorer, open the shortcut menu for the CancelAListOfTasks project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

If you don't want to download the project, you can review the MainWindow.xaml.cs files at the end of this topic.

Building the Example

To extend the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose CancelATask as the StartUp Project. Add the following changes to that project. Asterisks mark the changes in the program.

  1. Add a method to create a list of web addresses.

    // ***Add a method that creates a list of web addresses.  
    private List<string> SetUpURLList()  
    {  
        List<string> urls = new List<string>   
        {   
            "http://msdn.microsoft.com",  
            "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
            "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
            "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
            "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
            "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
            "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
        };  
        return urls;  
    }  
    
    
  2. Call the method in AccessTheWebAsync.

    // ***Call SetUpURLList to make a list of web addresses.  
    List<string> urlList = SetUpURLList();  
    
    
  3. Add the following loop in AccessTheWebAsync to process each web address in the list.

    // ***Add a loop to process the list of web addresses.  
    foreach (var url in urlList)  
    {  
        // GetAsync returns a Task<HttpResponseMessage>.   
        // Argument ct carries the message if the Cancel button is chosen.   
        // ***Note that the Cancel button can cancel all remaining downloads.  
        HttpResponseMessage response = await client.GetAsync(url, ct);  
    
        // Retrieve the website contents from the HttpResponseMessage.  
        byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
    
        resultsTextBox.Text +=  
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);  
    }  
    
    
  4. Because AccessTheWebAsync displays the lengths, the method doesn't need to return anything. Remove the return statement, and change the return type of the method to Task instead of Task<TResult>.

    async Task AccessTheWebAsync(CancellationToken ct)  
    
    

    Call the method from startButton_Click by using a statement instead of an expression.

    await AccessTheWebAsync(cts.Token);  
    
    
  5. If you don’t cancel the program, it produces the following output.

    Length of the downloaded string: 35939.  
    
    Length of the downloaded string: 237682.  
    
    Length of the downloaded string: 128607.  
    
    Length of the downloaded string: 158124.  
    
    Length of the downloaded string: 204890.  
    
    Length of the downloaded string: 175488.  
    
    Length of the downloaded string: 145790.  
    
    Downloads complete.  
    
    
    

    If you choose the Cancel button before the downloads are complete, the output contains the lengths of the downloads that completed before the cancellation.

    Length of the downloaded string: 35939.  
    
    Length of the downloaded string: 237682.  
    
    Length of the downloaded string: 128607.  
    
    Downloads canceled.  
    
    
    

The following sections contain the code for each of the previous examples. Notice that you must add a reference for System.Net.Http.

You can download the projects from Async Sample: Fine Tuning Your Application.

Cancel a Task Example

The following code is the complete MainWindow.xaml.cs file for the example that cancels a single task.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http.  
using System.Net.Http;  
  
// Add the following using directive for System.Threading.  
  
using System.Threading;  
namespace CancelATask  
{  
    public partial class MainWindow : Window  
    {  
        // ***Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            // ***Instantiate the CancellationTokenSource.  
            cts = new CancellationTokenSource();  
  
            resultsTextBox.Clear();  
  
            try  
            {  
                // ***Send a token to carry the message if cancellation is requested.  
                int contentLength = await AccessTheWebAsync(cts.Token);  
                resultsTextBox.Text +=  
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
            }  
            // *** If cancellation is requested, an OperationCanceledException results.  
            catch (OperationCanceledException)  
            {  
                resultsTextBox.Text += "\r\nDownload canceled.\r\n";  
            }  
            catch (Exception)  
            {  
                resultsTextBox.Text += "\r\nDownload failed.\r\n";  
            }  
  
            // ***Set the CancellationTokenSource to null when the download is complete.  
            cts = null;   
        }  
  
        // ***Add an event handler for the Cancel button.  
        private void cancelButton_Click(object sender, RoutedEventArgs e)  
        {  
            if (cts != null)  
            {  
                cts.Cancel();  
            }  
        }  
  
        // ***Provide a parameter for the CancellationToken.  
        async Task<int> AccessTheWebAsync(CancellationToken ct)  
        {  
            HttpClient client = new HttpClient();  
  
            resultsTextBox.Text +=  
                String.Format("\r\nReady to download.\r\n");  
  
            // You might need to slow things down to have a chance to cancel.  
            await Task.Delay(250);  
  
            // GetAsync returns a Task<HttpResponseMessage>.   
            // ***The ct argument carries the message if the Cancel button is chosen.  
            HttpResponseMessage response = await client.GetAsync("http://msdn.microsoft.com/en-us/library/dd470362.aspx", ct);  
  
            // Retrieve the website contents from the HttpResponseMessage.  
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
            // The result of the method is the length of the downloaded website.  
            return urlContents.Length;  
        }  
    }  
  
    // Output for a successful download:  
  
    // Ready to download.  
  
    // Length of the downloaded string: 158125.  
  
    // Or, if you cancel:  
  
    // Ready to download.  
  
    // Download canceled.  
}  

Cancel a List of Tasks Example

The following code is the complete MainWindow.xaml.cs file for the example that cancels a list of tasks.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http.  
using System.Net.Http;  
  
// Add the following using directive for System.Threading.  
using System.Threading;  
  
namespace CancelAListOfTasks  
{  
    public partial class MainWindow : Window  
    {  
        // Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Instantiate the CancellationTokenSource.  
            cts = new CancellationTokenSource();  
  
            resultsTextBox.Clear();  
  
            try  
            {  
                await AccessTheWebAsync(cts.Token);  
                // ***Small change in the display lines.  
                resultsTextBox.Text += "\r\nDownloads complete.";  
            }  
            catch (OperationCanceledException)  
            {  
                resultsTextBox.Text += "\r\nDownloads canceled.";  
            }  
            catch (Exception)  
            {  
                resultsTextBox.Text += "\r\nDownloads failed.";  
            }  
  
            // Set the CancellationTokenSource to null when the download is complete.  
            cts = null;  
        }  
  
        // Add an event handler for the Cancel button.  
        private void cancelButton_Click(object sender, RoutedEventArgs e)  
        {  
            if (cts != null)  
            {  
                cts.Cancel();  
            }  
        }  
  
        // Provide a parameter for the CancellationToken.  
        // ***Change the return type to Task because the method has no return statement.  
        async Task AccessTheWebAsync(CancellationToken ct)  
        {  
            // Declare an HttpClient object.  
            HttpClient client = new HttpClient();  
  
            // ***Call SetUpURLList to make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            // ***Add a loop to process the list of web addresses.  
            foreach (var url in urlList)  
            {  
                // GetAsync returns a Task<HttpResponseMessage>.   
                // Argument ct carries the message if the Cancel button is chosen.   
                // ***Note that the Cancel button can cancel all remaining downloads.  
                HttpResponseMessage response = await client.GetAsync(url, ct);  
  
                // Retrieve the website contents from the HttpResponseMessage.  
                byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
                resultsTextBox.Text +=  
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);  
            }  
        }  
  
        // ***Add a method that creates a list of web addresses.  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
    }  
  
    // Output if you do not choose to cancel:  
  
    //Length of the downloaded string: 35939.  
  
    //Length of the downloaded string: 237682.  
  
    //Length of the downloaded string: 128607.  
  
    //Length of the downloaded string: 158124.  
  
    //Length of the downloaded string: 204890.  
  
    //Length of the downloaded string: 175488.  
  
    //Length of the downloaded string: 145790.  
  
    //Downloads complete.  
  
    // Sample output if you choose to cancel:  
  
    //Length of the downloaded string: 35939.  
  
    //Length of the downloaded string: 237682.  
  
    //Length of the downloaded string: 128607.  
  
    //Downloads canceled.  
}






Cancel Async Tasks after a Period of Time (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can cancel an asynchronous operation after a period of time by using the CancellationTokenSource.CancelAfter method if you don't want to wait for the operation to finish. This method schedules the cancellation of any associated tasks that aren’t complete within the period of time that’s designated by the CancelAfter expression.

This example adds to the code that’s developed in Cancel an Async Task or a List of Tasks (C#) to download a list of websites and to display the length of the contents of each one.

System_CAPS_ICON_note.jpg Note

To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS.

  4. In Solution Explorer, open the shortcut menu for the CancelAfterTime project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

  6. Run the program several times to verify that the output might show output for all websites, no websites, or some web sites.

If you don't want to download the project, you can review the MainWindow.xaml.cs file at the end of this topic.

The example in this topic adds to the project that's developed in Cancel an Async Task or a List of Tasks (C#) to cancel a list of tasks. The example uses the same UI, although the Cancel button isn’t used explicitly.

To build the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose CancelAListOfTasks as the StartUp Project. Add the changes in this topic to that project.

To specify a maximum time before the tasks are marked as canceled, add a call to CancelAfter to startButton_Click, as the following example shows. The addition is marked with asterisks.

private async void startButton_Click(object sender, RoutedEventArgs e)  
{  
    // Instantiate the CancellationTokenSource.  
    cts = new CancellationTokenSource();  
  
    resultsTextBox.Clear();  
  
    try  
    {  
        // ***Set up the CancellationTokenSource to cancel after 2.5 seconds. (You  
        // can adjust the time.)  
        cts.CancelAfter(2500);  
  
        await AccessTheWebAsync(cts.Token);  
        resultsTextBox.Text += "\r\nDownloads succeeded.\r\n";  
    }  
    catch (OperationCanceledException)  
    {  
        resultsTextBox.Text += "\r\nDownloads canceled.\r\n";  
    }  
    catch (Exception)  
    {  
        resultsTextBox.Text += "\r\nDownloads failed.\r\n";  
    }  
  
    cts = null;   
}  

Run the program several times to verify that the output might show output for all websites, no websites, or some web sites. The following output is a sample.

Length of the downloaded string: 35990.  
  
Length of the downloaded string: 407399.  
  
Length of the downloaded string: 226091.  
  
Downloads canceled.  

The following code is the complete text of the MainWindow.xaml.cs file for the example. Asterisks mark the elements that were added for this example.

Notice that you must add a reference for System.Net.Http.

You can download the project from Async Sample: Fine Tuning Your Application.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http.  
using System.Net.Http;  
  
// Add the following using directive.  
using System.Threading;  
  
namespace CancelAfterTime  
{  
    public partial class MainWindow : Window  
    {  
        // Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Instantiate the CancellationTokenSource.  
            cts = new CancellationTokenSource();  
  
            resultsTextBox.Clear();  
  
            try  
            {  
                // ***Set up the CancellationTokenSource to cancel after 2.5 seconds. (You  
                // can adjust the time.)  
                cts.CancelAfter(2500);  
  
                await AccessTheWebAsync(cts.Token);  
                resultsTextBox.Text += "\r\nDownloads succeeded.\r\n";  
            }  
            catch (OperationCanceledException)  
            {  
                resultsTextBox.Text += "\r\nDownloads canceled.\r\n";  
            }  
            catch (Exception)  
            {  
                resultsTextBox.Text += "\r\nDownloads failed.\r\n";  
            }  
  
            cts = null;   
        }  
  
        // You can still include a Cancel button if you want to.  
        private void cancelButton_Click(object sender, RoutedEventArgs e)  
        {  
            if (cts != null)  
            {  
                cts.Cancel();  
            }  
        }  
  
        async Task AccessTheWebAsync(CancellationToken ct)  
        {  
            // Declare an HttpClient object.  
            HttpClient client = new HttpClient();  
  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            foreach (var url in urlList)  
            {  
                // GetAsync returns a Task<HttpResponseMessage>.   
                // Argument ct carries the message if the Cancel button is chosen.   
                // Note that the Cancel button cancels all remaining downloads.  
                HttpResponseMessage response = await client.GetAsync(url, ct);  
  
                // Retrieve the website contents from the HttpResponseMessage.  
                byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
                resultsTextBox.Text +=  
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);  
            }  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
    }  
  
    // Sample Output:  
  
    // Length of the downloaded string: 35990.  
  
    // Length of the downloaded string: 407399.  
  
    // Length of the downloaded string: 226091.  
  
    // Downloads canceled.  
}






Cancel Remaining Async Tasks after One Is Complete (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

By using the Task.WhenAny method together with a CancellationToken, you can cancel all remaining tasks when one task is complete. The WhenAnymethod takes an argument that’s a collection of tasks. The method starts all the tasks and returns a single task. The single task is complete when any task in the collection is complete.

This example demonstrates how to use a cancellation token in conjunction with WhenAny to hold onto the first task to finish from the collection of tasks and to cancel the remaining tasks. Each task downloads the contents of a website. The example displays the length of the contents of the first download to complete and cancels the other downloads.

System_CAPS_ICON_note.jpg Note

To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS.

  4. In Solution Explorer, open the shortcut menu for the CancelAfterOneTask project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

  6. Run the program several times to verify that different downloads finish first.

If you don't want to download the project, you can review the MainWindow.xaml.cs file at the end of this topic.

The example in this topic adds to the project that's developed in Cancel an Async Task or a List of Tasks (C#) to cancel a list of tasks. The example uses the same UI, although the Cancel button isn’t used explicitly.

To build the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose CancelAListOfTasks as the StartUp Project. Add the changes in this topic to that project.

In the MainWindow.xaml.cs file of the CancelAListOfTasks project, start the transition by moving the processing steps for each website from the loop in AccessTheWebAsync to the following async method.

/ ***Bundle the processing steps for a website into one async method.  
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)  
{  
    // GetAsync returns a Task<HttpResponseMessage>.   
    HttpResponseMessage response = await client.GetAsync(url, ct);  
  
    // Retrieve the website contents from the HttpResponseMessage.  
    byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
    return urlContents.Length;  
}  

In AccessTheWebAsync, this example uses a query, the ToArray<TSource> method, and the WhenAny method to create and start an array of tasks. The application of WhenAny to the array returns a single task that, when awaited, evaluates to the first task to reach completion in the array of tasks.

Make the following changes in AccessTheWebAsync. Asterisks mark the changes in the code file.

  1. Comment out or delete the loop.

  2. Create a query that, when executed, produces a collection of generic tasks. Each call to ProcessURLAsync returns a Task<TResult> where TResultis an integer.

    // ***Create a query that, when executed, returns a collection of tasks.  
    IEnumerable<Task<int>> downloadTasksQuery =  
        from url in urlList select ProcessURLAsync(url, client, ct);  
    
    
  3. Call ToArray to execute the query and start the tasks. The application of the WhenAny method in the next step would execute the query and start the tasks without using ToArray, but other methods might not. The safest practice is to force execution of the query explicitly.

    // ***Use ToArray to execute the query and start the download tasks.   
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
    
    
  4. Call WhenAny on the collection of tasks. WhenAny returns a Task(Of Task(Of Integer)) or Task<Task<int>>. That is, WhenAny returns a task that evaluates to a single Task(Of Integer) or Task<int> when it’s awaited. That single task is the first task in the collection to finish. The task that finished first is assigned to firstFinishedTask. The type of firstFinishedTask is Task<TResult> where TResult is an integer because that's the return type of ProcessURLAsync.

    // ***Call WhenAny and then await the result. The task that finishes   
    // first is assigned to firstFinishedTask.  
    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);  
    
    
  5. In this example, you’re interested only in the task that finishes first. Therefore, use CancellationTokenSource.Cancel to cancel the remaining tasks.

    // ***Cancel the rest of the downloads. You just want the first one.  
    cts.Cancel();  
    
    
  6. Finally, await firstFinishedTask to retrieve the length of the downloaded content.

    var length = await firstFinishedTask;  
    resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);  
    
    

Run the program several times to verify that different downloads finish first.

The following code is the complete MainWindow.xaml.cs file for the example. Asterisks mark the elements that were added for this example.

Notice that you must add a reference for System.Net.Http.

You can download the project from Async Sample: Fine Tuning Your Application.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http.  
using System.Net.Http;  
  
// Add the following using directive.  
using System.Threading;  
  
namespace CancelAfterOneTask  
{  
    public partial class MainWindow : Window  
    {  
        // Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Instantiate the CancellationTokenSource.  
            cts = new CancellationTokenSource();  
  
            resultsTextBox.Clear();  
  
            try  
            {  
                await AccessTheWebAsync(cts.Token);  
                resultsTextBox.Text += "\r\nDownload complete.";  
            }  
            catch (OperationCanceledException)  
            {  
                resultsTextBox.Text += "\r\nDownload canceled.";  
            }  
            catch (Exception)  
            {  
                resultsTextBox.Text += "\r\nDownload failed.";  
            }  
  
            // Set the CancellationTokenSource to null when the download is complete.  
            cts = null;  
        }  
  
        // You can still include a Cancel button if you want to.  
        private void cancelButton_Click(object sender, RoutedEventArgs e)  
        {  
            if (cts != null)  
            {  
                cts.Cancel();  
            }  
        }  
  
        // Provide a parameter for the CancellationToken.  
        async Task AccessTheWebAsync(CancellationToken ct)  
        {  
            HttpClient client = new HttpClient();  
  
            // Call SetUpURLList to make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            // ***Comment out or delete the loop.  
            //foreach (var url in urlList)  
            //{  
            //    // GetAsync returns a Task<HttpResponseMessage>.   
            //    // Argument ct carries the message if the Cancel button is chosen.   
            //    // ***Note that the Cancel button can cancel all remaining downloads.  
            //    HttpResponseMessage response = await client.GetAsync(url, ct);  
  
            //    // Retrieve the website contents from the HttpResponseMessage.  
            //    byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
            //    resultsTextBox.Text +=  
            //        String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);  
            //}  
  
            // ***Create a query that, when executed, returns a collection of tasks.  
            IEnumerable<Task<int>> downloadTasksQuery =  
                from url in urlList select ProcessURLAsync(url, client, ct);  
  
            // ***Use ToArray to execute the query and start the download tasks.   
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();  
  
            // ***Call WhenAny and then await the result. The task that finishes   
            // first is assigned to firstFinishedTask.  
            Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);  
  
            // ***Cancel the rest of the downloads. You just want the first one.  
            cts.Cancel();  
  
            // ***Await the first completed task and display the results.   
            // Run the program several times to demonstrate that different  
            // websites can finish first.  
            var length = await firstFinishedTask;  
            resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);  
        }  
  
        // ***Bundle the processing steps for a website into one async method.  
        async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)  
        {  
            // GetAsync returns a Task<HttpResponseMessage>.   
            HttpResponseMessage response = await client.GetAsync(url, ct);  
  
            // Retrieve the website contents from the HttpResponseMessage.  
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
            return urlContents.Length;  
        }  
  
        // Add a method that creates a list of web addresses.  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
    }  
    // Sample output:  
  
    // Length of the downloaded website:  158856  
  
    // Download complete.  
}






Start Multiple Async Tasks and Process Them As They Complete (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

By using Task.WhenAny, you can start multiple tasks at the same time and process them one by one as they’re completed rather than process them in the order in which they're started.

The following example uses a query to create a collection of tasks. Each task downloads the contents of a specified website. In each iteration of a while loop, an awaited call to WhenAny returns the task in the collection of tasks that finishes its download first. That task is removed from the collection and processed. The loop repeats until the collection contains no more tasks.

System_CAPS_ICON_note.jpg Note

To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio.

  2. On the menu bar, choose FileOpenProject/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS.

  4. In Solution Explorer, open the shortcut menu for the ProcessTasksAsTheyFinish project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

  6. Run the project several times to verify that the downloaded lengths don't always appear in the same order.

If you don't want to download the project, you can review the MainWindow.xaml.cs file at the end of this topic.

This example adds to the code that’s developed in Cancel Remaining Async Tasks after One Is Complete (C#)Cancel Remaining Async Tasks after One Is Complete and uses the same UI.

To build the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose CancelAfterOneTask as the StartUp Project. Add the changes in this topic to the AccessTheWebAsync method in that project. The changes are marked with asterisks.

The CancelAfterOneTask project already includes a query that, when executed, creates a collection of tasks. Each call to ProcessURLAsync in the following code returns a Task<TResult> where TResult is an integer.

IEnumerable<Task<int>> downloadTasksQuery =  
    from url in urlList select ProcessURL(url, client, ct);  

In the MainWindow.xaml.cs file of the project, make the following changes to the AccessTheWebAsync method.

  • Execute the query by applying Enumerable.ToList<TSource> instead of ToArray<TSource>.

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();  
    
    
  • Add a while loop that performs the following steps for each task in the collection.

    1. Awaits a call to WhenAny to identify the first task in the collection to finish its download.

      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);  
      
      
    2. Removes that task from the collection.

      downloadTasks.Remove(firstFinishedTask);  
      
      
    3. Awaits firstFinishedTask, which is returned by a call to ProcessURLAsync. The firstFinishedTask variable is a Task<TResult> where TReturn is an integer. The task is already complete, but you await it to retrieve the length of the downloaded website, as the following example shows.

      int length = await firstFinishedTask;  
      resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);  
      VBCopy Code  
      Dim length = Await firstFinishedTask  
      
      

You should run the project several times to verify that the downloaded lengths don't always appear in the same order.

System_CAPS_ICON_caution.jpg Caution

You can use WhenAny in a loop, as described in the example, to solve problems that involve a small number of tasks. However, other approaches are more efficient if you have a large number of tasks to process. For more information and examples, see Processing Tasks as they complete.

The following code is the complete text of the MainWindow.xaml.cs file for the example. Asterisks mark the elements that were added for this example.

Notice that you must add a reference for System.Net.Http.

You can download the project from Async Sample: Fine Tuning Your Application.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http.  
using System.Net.Http;  
  
// Add the following using directive.  
using System.Threading;  
  
namespace ProcessTasksAsTheyFinish  
{  
    public partial class MainWindow : Window  
    {  
        // Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        private async void startButton_Click(object sender, RoutedEventArgs e)  
        {  
            resultsTextBox.Clear();  
  
            // Instantiate the CancellationTokenSource.  
            cts = new CancellationTokenSource();  
  
            try  
            {  
                await AccessTheWebAsync(cts.Token);  
                resultsTextBox.Text += "\r\nDownloads complete.";  
            }  
            catch (OperationCanceledException)  
            {  
                resultsTextBox.Text += "\r\nDownloads canceled.\r\n";  
            }  
            catch (Exception)  
            {  
                resultsTextBox.Text += "\r\nDownloads failed.\r\n";  
            }  
  
            cts = null;  
        }  
  
        private void cancelButton_Click(object sender, RoutedEventArgs e)  
        {  
            if (cts != null)  
            {  
                cts.Cancel();  
            }  
        }  
  
        async Task AccessTheWebAsync(CancellationToken ct)  
        {  
            HttpClient client = new HttpClient();  
  
            // Make a list of web addresses.  
            List<string> urlList = SetUpURLList();  
  
            // ***Create a query that, when executed, returns a collection of tasks.  
            IEnumerable<Task<int>> downloadTasksQuery =  
                from url in urlList select ProcessURL(url, client, ct);  
  
            // ***Use ToList to execute the query and start the tasks.   
            List<Task<int>> downloadTasks = downloadTasksQuery.ToList();  
  
            // ***Add a loop to process the tasks one at a time until none remain.  
            while (downloadTasks.Count > 0)  
            {  
                    // Identify the first task that completes.  
                    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);  
  
                    // ***Remove the selected task from the list so that you don't  
                    // process it more than once.  
                    downloadTasks.Remove(firstFinishedTask);  
  
                    // Await the completed task.  
                    int length = await firstFinishedTask;  
                    resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);  
            }  
        }  
  
        private List<string> SetUpURLList()  
        {  
            List<string> urls = new List<string>   
            {   
                "http://msdn.microsoft.com",  
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",  
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",  
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",  
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
            };  
            return urls;  
        }  
  
        async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)  
        {  
            // GetAsync returns a Task<HttpResponseMessage>.   
            HttpResponseMessage response = await client.GetAsync(url, ct);  
  
            // Retrieve the website contents from the HttpResponseMessage.  
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
            return urlContents.Length;  
        }  
    }  
}  
  
// Sample Output:  
  
// Length of the download:  226093  
// Length of the download:  412588  
// Length of the download:  175490  
// Length of the download:  204890  
// Length of the download:  158855  
// Length of the download:  145790  
// Length of the download:  44908  
// Downloads complete.






Handling Reentrancy in Async Apps (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

When you include asynchronous code in your app, you should consider and possibly prevent reentrancy, which refers to reentering an asynchronous operation before it has completed. If you don't identify and handle possibilities for reentrancy, it can cause unexpected results.

In this topic

  • Recognizing Reentrancy

  • Handling Reentrancy

    • Disable the Start Button

    • Cancel and Restart the Operation

    • Run Multiple Operations and Queue the Output

  • Reviewing and Running the Example App

System_CAPS_ICON_note.jpg Note

To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

In the example in this topic, users choose a Start button to initiate an asynchronous app that downloads a series of websites and calculates the total number of bytes that are downloaded. A synchronous version of the example would respond the same way regardless of how many times a user chooses the button because, after the first time, the UI thread ignores those events until the app finishes running. In an asynchronous app, however, the UI thread continues to respond, and you might reenter the asynchronous operation before it has completed.

The following example shows the expected output if the user chooses the Start button only once. A list of the downloaded websites appears with the size, in bytes, of each site. The total number of bytes appears at the end.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  
  
TOTAL bytes returned:  890591  

However, if the user chooses the button more than once, the event handler is invoked repeatedly, and the download process is reentered each time. As a result, several asynchronous operations are running at the same time, the output interleaves the results, and the total number of bytes is confusing.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
3. msdn.microsoft.com/en-us/library/jj155761.aspx                29019  
7. msdn.microsoft.com                                            42972  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
8. msdn.microsoft.com/library/ff730837.aspx               146159  
  
TOTAL bytes returned:  890591  
  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
7. msdn.microsoft.com                                            42972  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
8. msdn.microsoft.com/library/ff730837.aspx               146159  
  
TOTAL bytes returned:  890591  
  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  
  
TOTAL bytes returned:  890591  

You can review the code that produces this output by scrolling to the end of this topic. You can experiment with the code by downloading the solution to your local computer and then running the WebsiteDownload project or by using the code at the end of this topic to create your own project For more information and instructions, see Reviewing and Running the Example App.

You can handle reentrancy in a variety of ways, depending on what you want your app to do. This topic presents the following examples:

  • Disable the Start Button

    Disable the Start button while the operation is running so that the user can't interrupt it.

  • Cancel and Restart the Operation

    Cancel any operation that is still running when the user chooses the Start button again, and then let the most recently requested operation continue.

  • Run Multiple Operations and Queue the Output

    Allow all requested operations to run asynchronously, but coordinate the display of output so that the results from each operation appear together and in order.

Disable the Start Button

You can block the Start button while an operation is running by disabling the button at the top of the StartButton_Click event handler. You can then reenable the button from within a finally block when the operation finishes so that users can run the app again.

The following code shows these changes, which are marked with asterisks. You can add the changes to the code at the end of this topic, or you can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. The project name is DisableStartButton.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // This line is commented out to make the results clearer in the output.  
    //ResultsTextBox.Text = "";  
  
    // ***Disable the Start button until the downloads are complete.   
    StartButton.IsEnabled = false;   
  
    try  
    {  
        await AccessTheWebAsync();  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.";  
    }  
    // ***Enable the Start button in case you want to run the program again.   
    finally  
    {  
        StartButton.IsEnabled = true;  
    }  
}  

As a result of the changes, the button doesn't respond while AccessTheWebAsync is downloading the websites, so the process can’t be reentered.

Cancel and Restart the Operation

Instead of disabling the Start button, you can keep the button active but, if the user chooses that button again, cancel the operation that's already running and let the most recently started operation continue.

For more information about cancellation, see Fine-Tuning Your Async Application (C#).

To set up this scenario, make the following changes to the basic code that is provided in Reviewing and Running the Example App. You also can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. The name of this project is CancelAndRestart.

  1. Declare a CancellationTokenSource variable, cts, that’s in scope for all methods.

    public partial class MainWindow : Window   // Or class MainPage  
    {  
        // *** Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
    
    
  2. In StartButton_Click, determine whether an operation is already underway. If the value of cts is null, no operation is already active. If the value isn't null, the operation that is already running is canceled.

    // *** If a download process is already underway, cancel it.  
    if (cts != null)  
    {  
        cts.Cancel();  
    }  
    
    
  3. Set cts to a different value that represents the current process.

    // *** Now set cts to a new value that you can use to cancel the current process  
    // if the button is chosen again.  
    CancellationTokenSource newCTS = new CancellationTokenSource();  
    cts = newCTS;  
    
    
  4. At the end of StartButton_Click, the current process is complete, so set the value of cts back to null.

    // *** When the process is complete, signal that another process can begin.  
    if (cts == newCTS)  
        cts = null;  
    
    

The following code shows all the changes in StartButton_Click. The additions are marked with asterisks.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // This line is commented out to make the results clearer in the output.  
    //ResultsTextBox.Clear();  
  
    // *** If a download process is already underway, cancel it.  
    if (cts != null)  
    {  
        cts.Cancel();  
    }  
  
    // *** Now set cts to cancel the current process if the button is chosen again.  
    CancellationTokenSource newCTS = new CancellationTokenSource();  
    cts = newCTS;  
  
    try  
    {  
        // ***Send cts.Token to carry the message if there is a cancellation request.  
        await AccessTheWebAsync(cts.Token);  
  
    }  
    // *** Catch cancellations separately.  
    catch (OperationCanceledException)  
    {  
        ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.\r\n";  
    }  
    // *** When the process is complete, signal that another process can proceed.  
    if (cts == newCTS)  
        cts = null;  
}  

In AccessTheWebAsync, make the following changes.

  • Add a parameter to accept the cancellation token from StartButton_Click.

  • Use the GetAsync method to download the websites because GetAsync accepts a CancellationToken argument.

  • Before calling DisplayResults to display the results for each downloaded website, check ct to verify that the current operation hasn’t been canceled.

The following code shows these changes, which are marked with asterisks.

// *** Provide a parameter for the CancellationToken from StartButton_Click.  
async Task AccessTheWebAsync(CancellationToken ct)  
{  
    // Declare an HttpClient object.  
    HttpClient client = new HttpClient();  
  
    // Make a list of web addresses.  
    List<string> urlList = SetUpURLList();  
  
    var total = 0;  
    var position = 0;  
  
    foreach (var url in urlList)  
    {  
        // *** Use the HttpClient.GetAsync method because it accepts a   
        // cancellation token.  
        HttpResponseMessage response = await client.GetAsync(url, ct);  
  
        // *** Retrieve the website contents from the HttpResponseMessage.  
        byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  
  
        // *** Check for cancellations before displaying information about the   
        // latest site.   
        ct.ThrowIfCancellationRequested();  
  
        DisplayResults(url, urlContents, ++position);  
  
        // Update the total.  
        total += urlContents.Length;  
    }  
  
    // Display the total count for all of the websites.  
    ResultsTextBox.Text +=  
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
}     

If you choose the Start button several times while this app is running, it should produce results that resemble the following output.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               122505  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
Download canceled.  
  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
Download canceled.  
  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  
  
TOTAL bytes returned:  890591  

To eliminate the partial lists, uncomment the first line of code in StartButton_Click to clear the text box each time the user restarts the operation.

Run Multiple Operations and Queue the Output

This third example is the most complicated in that the app starts another asynchronous operation each time that the user chooses the Start button, and all the operations run to completion. All the requested operations download websites from the list asynchronously, but the output from the operations is presented sequentially. That is, the actual downloading activity is interleaved, as the output in Recognizing Reentrancy shows, but the list of results for each group is presented separately.

The operations share a global TaskpendingWork, which serves as a gatekeeper for the display process.

You can run this example by pasting the changes into the code in Building the App, or you can follow the instructions in Downloading the App to download the sample and then run the QueueResults project.

The following output shows the result if the user chooses the Start button only once. The letter label, A, indicates that the result is from the first time the Start button is chosen. The numbers show the order of the URLs in the list of download targets.

  
#Starting group A.  
#Task assigned for group A.  
  
A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
A-2. msdn.microsoft.com/library/aa578028.aspx               209858  
A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
A-4. msdn.microsoft.com/library/hh290140.aspx               119027  
A-5. msdn.microsoft.com/library/hh524395.aspx                71260  
A-6. msdn.microsoft.com/library/ms404677.aspx               199186  
A-7. msdn.microsoft.com                                            53266  
A-8. msdn.microsoft.com/library/ff730837.aspx               148020  
  
TOTAL bytes returned:  918876  
  
#Group A is complete.  

If the user chooses the Start button three times, the app produces output that resembles the following lines. The information lines that start with a pound sign (#) trace the progress of the application.

#Starting group A.  
#Task assigned for group A.  
  
A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
A-2. msdn.microsoft.com/library/aa578028.aspx               207089  
A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
A-4. msdn.microsoft.com/library/hh290140.aspx               119027  
A-5. msdn.microsoft.com/library/hh524395.aspx                71259  
A-6. msdn.microsoft.com/library/ms404677.aspx               199185  
  
#Starting group B.  
#Task assigned for group B.  
  
A-7. msdn.microsoft.com                                            53266  
  
#Starting group C.  
#Task assigned for group C.  
  
A-8. msdn.microsoft.com/library/ff730837.aspx               148010  
  
TOTAL bytes returned:  916095  
  
B-1. msdn.microsoft.com/library/hh191443.aspx                87389  
B-2. msdn.microsoft.com/library/aa578028.aspx               207089  
B-3. msdn.microsoft.com/library/jj155761.aspx                30870  
B-4. msdn.microsoft.com/library/hh290140.aspx               119027  
B-5. msdn.microsoft.com/library/hh524395.aspx                71260  
B-6. msdn.microsoft.com/library/ms404677.aspx               199186  
  
#Group A is complete.  
  
B-7. msdn.microsoft.com                                            53266  
B-8. msdn.microsoft.com/library/ff730837.aspx               148010  
  
TOTAL bytes returned:  916097  
  
C-1. msdn.microsoft.com/library/hh191443.aspx                87389  
C-2. msdn.microsoft.com/library/aa578028.aspx               207089  
  
#Group B is complete.  
  
C-3. msdn.microsoft.com/library/jj155761.aspx                30870  
C-4. msdn.microsoft.com/library/hh290140.aspx               119027  
C-5. msdn.microsoft.com/library/hh524395.aspx                72765  
C-6. msdn.microsoft.com/library/ms404677.aspx               199186  
C-7. msdn.microsoft.com                                            56190  
C-8. msdn.microsoft.com/library/ff730837.aspx               148010  
  
TOTAL bytes returned:  920526  
  
#Group C is complete.  
  

Groups B and C start before group A has finished, but the output for the each group appears separately. All the output for group A appears first, followed by all the output for group B, and then all the output for group C. The app always displays the groups in order and, for each group, always displays the information about the individual websites in the order that the URLs appear in the list of URLs.

However, you can't predict the order in which the downloads actually happen. After multiple groups have been started, the download tasks that they generate are all active. You can't assume that A-1 will be downloaded before B-1, and you can't assume that A-1 will be downloaded before A-2.

Global Definitions

The sample code contains the following two global declarations that are visible from all methods.

public partial class MainWindow : Window  // Class MainPage in Windows Store app.  
{  
    // ***Declare the following variables where all methods can access them.   
    private Task pendingWork = null;     
    private char group = (char)('A' - 1);  

The Task variable, pendingWork, oversees the display process and prevents any group from interrupting another group's display operation. The character variable, group, labels the output from different groups to verify that results appear in the expected order.

The Click Event Handler

The event handler, StartButton_Click, increments the group letter each time the user chooses the Start button. Then the handler calls AccessTheWebAsync to run the downloading operation.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // ***Verify that each group's results are displayed together, and that  
    // the groups display in order, by marking each group with a letter.  
    group = (char)(group + 1);  
    ResultsTextBox.Text += string.Format("\r\n\r\n#Starting group {0}.", group);  
  
    try  
    {  
        // *** Pass the group value to AccessTheWebAsync.  
        char finishedGroup = await AccessTheWebAsync(group);  
  
        // The following line verifies a successful return from the download and  
        // display procedures.   
        ResultsTextBox.Text += string.Format("\r\n\r\n#Group {0} is complete.\r\n", finishedGroup);  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.";  
    }  
}  

The AccessTheWebAsync Method

This example splits AccessTheWebAsync into two methods. The first method, AccessTheWebAsync, starts all the download tasks for a group and sets up pendingWork to control the display process. The method uses a Language Integrated Query (LINQ query) and ToArray<TSource> to start all the download tasks at the same time.

AccessTheWebAsync then calls FinishOneGroupAsync to await the completion of each download and display its length.

FinishOneGroupAsync returns a task that's assigned to pendingWork in AccessTheWebAsync. That value prevents interruption by another operation before the task is complete.

private async Task<char> AccessTheWebAsync(char grp)  
{  
    HttpClient client = new HttpClient();  
  
    // Make a list of the web addresses to download.  
    List<string> urlList = SetUpURLList();  
  
    // ***Kick off the downloads. The application of ToArray activates all the download tasks.  
    Task<byte[]>[] getContentTasks = urlList.Select(url => client.GetByteArrayAsync(url)).ToArray();  
  
    // ***Call the method that awaits the downloads and displays the results.  
    // Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.  
    pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp);  
  
    ResultsTextBox.Text += string.Format("\r\n#Task assigned for group {0}. Download tasks are active.\r\n", grp);  
  
    // ***This task is complete when a group has finished downloading and displaying.  
    await pendingWork;  
  
    // You can do other work here or just return.  
    return grp;  
}  

The FinishOneGroupAsync Method

This method cycles through the download tasks in a group, awaiting each one, displaying the length of the downloaded website, and adding the length to the total.

The first statement in FinishOneGroupAsync uses pendingWork to make sure that entering the method doesn't interfere with an operation that is already in the display process or that's already waiting. If such an operation is in progress, the entering operation must wait its turn.

private async Task FinishOneGroupAsync(List<string> urls, Task<byte[]>[] contentTasks, char grp)  
{  
    // ***Wait for the previous group to finish displaying results.  
    if (pendingWork != null) await pendingWork;  
  
    int total = 0;  
  
    // contentTasks is the array of Tasks that was created in AccessTheWebAsync.  
    for (int i = 0; i < contentTasks.Length; i++)  
    {  
        // Await the download of a particular URL, and then display the URL and  
        // its length.  
        byte[] content = await contentTasks[i];  
        DisplayResults(urls[i], content, i, grp);  
        total += content.Length;  
    }  
  
    // Display the total count for all of the websites.  
    ResultsTextBox.Text +=  
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
}  

You can run this example by pasting the changes into the code in Building the App, or you can follow the instructions in Downloading the App to download the sample, and then run the QueueResults project.

Points of Interest

The information lines that start with a pound sign (#) in the output clarify how this example works.

The output shows the following patterns.

  • A group can be started while a previous group is displaying its output, but the display of the previous group's output isn't interrupted.

    #Starting group A.  
    #Task assigned for group A. Download tasks are active.  
    
    A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
    A-2. msdn.microsoft.com/library/aa578028.aspx               207089  
    A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
    A-4. msdn.microsoft.com/library/hh290140.aspx               119037  
    A-5. msdn.microsoft.com/library/hh524395.aspx                71260  
    
    #Starting group B.  
    #Task assigned for group B. Download tasks are active.  
    
    A-6. msdn.microsoft.com/library/ms404677.aspx               199186  
    A-7. msdn.microsoft.com                                            53078  
    A-8. msdn.microsoft.com/library/ff730837.aspx               148010  
    
    TOTAL bytes returned:  915919  
    
    B-1. msdn.microsoft.com/library/hh191443.aspx                87388  
    B-2. msdn.microsoft.com/library/aa578028.aspx               207089  
    B-3. msdn.microsoft.com/library/jj155761.aspx                30870  
    
    #Group A is complete.  
    
    B-4. msdn.microsoft.com/library/hh290140.aspx               119027  
    B-5. msdn.microsoft.com/library/hh524395.aspx                71260  
    B-6. msdn.microsoft.com/library/ms404677.aspx               199186  
    B-7. msdn.microsoft.com                                            53078  
    B-8. msdn.microsoft.com/library/ff730837.aspx               148010  
    
    TOTAL bytes returned:  915908  
    
    
  • The pendingWork task is null at the start of FinishOneGroupAsync only for group A, which started first. Group A hasn’t yet completed an await expression when it reaches FinishOneGroupAsync. Therefore, control hasn't returned to AccessTheWebAsync, and the first assignment to pendingWork hasn't occurred.

  • The following two lines always appear together in the output. The code is never interrupted between starting a group's operation in StartButton_Click and assigning a task for the group to pendingWork.

    #Starting group B.  
    #Task assigned for group B. Download tasks are active.  
    
    

    After a group enters StartButton_Click, the operation doesn't complete an await expression until the operation enters FinishOneGroupAsync. Therefore, no other operation can gain control during that segment of code.

To better understand the example app, you can download it, build it yourself, or review the code at the end of this topic without implementing the app.

System_CAPS_ICON_note.jpg Note

To run the example as a Windows Presentation Foundation (WPF) desktop app, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Downloading the App

  1. Download the compressed file from Async Samples: Reentrancy in .NET Desktop Apps.

  2. Decompress the file that you downloaded, and then start Visual Studio.

  3. On the menu bar, choose FileOpenProject/Solution.

  4. Navigate to the folder that holds the decompressed sample code, and then open the solution (.sln) file.

  5. In Solution Explorer, open the shortcut menu for the project that you want to run, and then choose Set as StartUpProject.

  6. Choose the CTRL+F5 keys to build and run the project.

Building the App

The following section provides the code to build the example as a WPF app.

To build a WPF app
  1. Start Visual Studio.

  2. On the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  3. In the Installed Templates pane, expand Visual C#, and then expand Windows.

  4. In the list of project types, choose WPF Application.

  5. Name the project WebsiteDownloadWPF, and then choose the OK button.

    The new project appears in Solution Explorer.

  6. In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  7. In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window x:Class="WebsiteDownloadWPF.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:local="using:WebsiteDownloadWPF"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        mc:Ignorable="d">  
    
        <Grid Width="517" Height="360">  
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="-1,0,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="53" Background="#FFA89B9B" FontSize="36" Width="518"  />  
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="-1,53,0,-36" TextWrapping="Wrap" VerticalAlignment="Top" Height="343" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="518" FontFamily="Lucida Console" />  
        </Grid>  
    </Window>  
    
    

    A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  8. Add a reference for System.Net.Http.

  9. In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  10. In MainWindow.xaml.cs, replace the code with the following code.

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Threading.Tasks;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Navigation;  
    using System.Windows.Shapes;  
    
    // Add the following using directives, and add a reference for System.Net.Http.  
    using System.Net.Http;  
    using System.Threading;  
    
    namespace WebsiteDownloadWPF  
    {  
        public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
            }  
    
            private async void StartButton_Click(object sender, RoutedEventArgs e)  
            {  
                // This line is commented out to make the results clearer in the output.  
                //ResultsTextBox.Text = "";  
    
                try  
                {  
                    await AccessTheWebAsync();  
                }  
                catch (Exception)  
                {  
                    ResultsTextBox.Text += "\r\nDownloads failed.";  
                }  
            }  
    
            private async Task AccessTheWebAsync()  
            {  
                // Declare an HttpClient object.  
                HttpClient client = new HttpClient();  
    
                // Make a list of web addresses.  
                List<string> urlList = SetUpURLList();  
    
                var total = 0;  
                var position = 0;  
    
                foreach (var url in urlList)  
                {  
                    // GetByteArrayAsync returns a task. At completion, the task  
                    // produces a byte array.  
                    byte[] urlContents = await client.GetByteArrayAsync(url);  
    
                    DisplayResults(url, urlContents, ++position);  
    
                    // Update the total.  
                    total += urlContents.Length;  
                }  
    
                // Display the total count for all of the websites.  
                ResultsTextBox.Text +=  
                    string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
            }  
    
            private List<string> SetUpURLList()  
            {  
                List<string> urls = new List<string>   
                {   
                    "http://msdn.microsoft.com/en-us/library/hh191443.aspx",  
                    "http://msdn.microsoft.com/en-us/library/aa578028.aspx",  
                    "http://msdn.microsoft.com/en-us/library/jj155761.aspx",  
                    "http://msdn.microsoft.com/en-us/library/hh290140.aspx",  
                    "http://msdn.microsoft.com/en-us/library/hh524395.aspx",  
                    "http://msdn.microsoft.com/en-us/library/ms404677.aspx",  
                    "http://msdn.microsoft.com",  
                    "http://msdn.microsoft.com/en-us/library/ff730837.aspx"  
                };  
                return urls;  
            }  
    
            private void DisplayResults(string url, byte[] content, int pos)  
            {  
                // Display the length of each website. The string format is designed  
                // to be used with a monospaced font, such as Lucida Console or   
                // Global Monospace.  
    
                // Strip off the "http://".  
                var displayURL = url.Replace("http://", "");  
                // Display position in the URL list, the URL, and the number of bytes.  
                ResultsTextBox.Text += string.Format("\n{0}. {1,-58} {2,8}", pos, displayURL, content.Length);  
            }  
        }  
    }  
    
    
  11. Choose the CTRL+F5 keys to run the program, and then choose the Start button several times.

  12. Make the changes from Disable the Start ButtonCancel and Restart the Operation, or Run Multiple Operations and Queue the Output to handle the reentrancy.






Using Async for File Access (C#)


Updated: July 20, 2015

System_CAPS_ICON_note.jpg Note

For the latest documentation on C#, visit the C# Guide on docs.microsoft.com.

You can use the async feature to access files. By using the async feature, you can call into asynchronous methods without using callbacks or splitting your code across multiple methods or lambda expressions. To make synchronous code asynchronous, you just call an asynchronous method instead of a synchronous method and add a few keywords to the code.

You might consider the following reasons for adding asynchrony to file access calls:

  • Asynchrony makes UI applications more responsive because the UI thread that launches the operation can perform other work. If the UI thread must execute code that takes a long time (for example, more than 50 milliseconds), the UI may freeze until the I/O is complete and the UI thread can again process keyboard and mouse input and other events.

  • Asynchrony improves the scalability of ASP.NET and other server-based applications by reducing the need for threads. If the application uses a dedicated thread per response and a thousand requests are being handled simultaneously, a thousand threads are needed. Asynchronous operations often don’t need to use a thread during the wait. They use the existing I/O completion thread briefly at the end.

  • The latency of a file access operation might be very low under current conditions, but the latency may greatly increase in the future. For example, a file may be moved to a server that's across the world.

  • The added overhead of using the Async feature is small.

  • Asynchronous tasks can easily be run in parallel.

To run the examples in this topic, you can create a WPF Application or a Windows Forms Application and then add a Button. In the button's Clickevent, add a call to the first method in each example.

In the following examples, include the following using statements.

using System;  
using System.Collections.Generic;  
using System.Diagnostics;  
using System.IO;  
using System.Text;  
using System.Threading.Tasks;  

The examples in this topic use the FileStream class, which has an option that causes asynchronous I/O to occur at the operating system level. By using this option, you can avoid blocking a ThreadPool thread in many cases. To enable this option, you specify the useAsync=true or options=FileOptions.Asynchronous argument in the constructor call.

You can’t use this option with StreamReader and StreamWriter if you open them directly by specifying a file path. However, you can use this option if you provide them a Stream that the FileStream class opened. Note that asynchronous calls are faster in UI apps even if a ThreadPool thread is blocked, because the UI thread isn’t blocked during the wait.

The following example writes text to a file. At each await statement, the method immediately exits. When the file I/O is complete, the method resumes at the statement that follows the await statement. Note that the async modifier is in the definition of methods that use the await statement.

public async void ProcessWrite()  
{  
    string filePath = @"temp2.txt";  
    string text = "Hello World\r\n";  
  
    await WriteTextAsync(filePath, text);  
}  
  
private async Task WriteTextAsync(string filePath, string text)  
{  
    byte[] encodedText = Encoding.Unicode.GetBytes(text);  
  
    using (FileStream sourceStream = new FileStream(filePath,  
        FileMode.Append, FileAccess.Write, FileShare.None,  
        bufferSize: 4096, useAsync: true))  
    {  
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);  
    };  
}  

The original example has the statement await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, which is a contraction of the following two statements:

Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);  
await theTask;  

The first statement returns a task and causes file processing to start. The second statement with the await causes the method to immediately exit and return a different task. When the file processing later completes, execution returns to the statement that follows the await. For more information, seeControl Flow in Async Programs (C#).

The following example reads text from a file. The text is buffered and, in this case, placed into a StringBuilder. Unlike in the previous example, the evaluation of the await produces a value. The ReadAsync method returns a Task<Int32>, so the evaluation of the await produces an Int32 value (numRead) after the operation completes. For more information, see Async Return Types (C#).

public async void ProcessRead()  
{  
    string filePath = @"temp2.txt";  
  
    if (File.Exists(filePath) == false)  
    {  
        Debug.WriteLine("file not found: " + filePath);  
    }  
    else  
    {  
        try  
        {  
            string text = await ReadTextAsync(filePath);  
            Debug.WriteLine(text);  
        }  
        catch (Exception ex)  
        {  
            Debug.WriteLine(ex.Message);  
        }  
    }  
}  
  
private async Task<string> ReadTextAsync(string filePath)  
{  
    using (FileStream sourceStream = new FileStream(filePath,  
        FileMode.Open, FileAccess.Read, FileShare.Read,  
        bufferSize: 4096, useAsync: true))  
    {  
        StringBuilder sb = new StringBuilder();  
  
        byte[] buffer = new byte[0x1000];  
        int numRead;  
        while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)  
        {  
            string text = Encoding.Unicode.GetString(buffer, 0, numRead);  
            sb.Append(text);  
        }  
  
        return sb.ToString();  
    }  
}  

The following example demonstrates parallel processing by writing 10 text files. For each file, the WriteAsync method returns a task that is then added to a list of tasks. The await Task.WhenAll(tasks); statement exits the method and resumes within the method when file processing is complete for all of the tasks.

The example closes all FileStream instances in a finally block after the tasks are complete. If each FileStream was instead created in a usingstatement, the FileStream might be disposed of before the task was complete.

Note that any performance boost is almost entirely from the parallel processing and not the asynchronous processing. The advantages of asynchrony are that it doesn’t tie up multiple threads, and that it doesn’t tie up the user interface thread.

public async void ProcessWriteMult()  
{  
    string folder = @"tempfolder\";  
    List<Task> tasks = new List<Task>();  
    List<FileStream> sourceStreams = new List<FileStream>();  
  
    try  
    {  
        for (int index = 1; index <= 10; index++)  
        {  
            string text = "In file " + index.ToString() + "\r\n";  
  
            string fileName = "thefile" + index.ToString("00") + ".txt";  
            string filePath = folder + fileName;  
  
            byte[] encodedText = Encoding.Unicode.GetBytes(text);  
  
            FileStream sourceStream = new FileStream(filePath,  
                FileMode.Append, FileAccess.Write, FileShare.None,  
                bufferSize: 4096, useAsync: true);  
  
            Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);  
            sourceStreams.Add(sourceStream);  
  
            tasks.Add(theTask);  
        }  
  
        await Task.WhenAll(tasks);  
    }  
  
    finally  
    {  
        foreach (FileStream sourceStream in sourceStreams)  
        {  
            sourceStream.Close();  
        }  
    }  
}  

When using the WriteAsync and ReadAsync methods, you can specify a CancellationToken, which you can use to cancel the operation mid-stream. For more information, see Fine-Tuning Your Async Application (C#) and Cancellation in Managed Threads.













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

Task  (0) 2017.04.22
Task-based Asynchronous Programming  (0) 2017.04.22
Developing Windows Service Applications  (0) 2017.04.02
Events  (0) 2017.03.29
Common I/O Tasks  (0) 2017.03.15
:
Posted by 지훈2
2017. 4. 2. 11:07

Developing Windows Service Applications 프로그래밍/C#2017. 4. 2. 11:07


  1. Developing Windows Service Applications
  2. Introduction to Windows Service Applications
  3. Walkthrough: Creating a Windows Service Application in the Component Designer
  4. Service Application Programming Architecture
  5. How to: Create Windows Services
  6. How to: Write Services Programmatically
  7. How to: Add Installers to Your Service Application
  8. How to: Specify the Security Context for Services
  9. How to: Install and Uninstall Services
  10. How to: Start Services
  11. How to: Pause a Windows Service (Visual Basic)
  12. How to: Continue a Windows Service (Visual Basic)
  13. How to: Debug Windows Service Applications
  14. How to: Log Information About Services
  15. Troubleshooting: Debugging Windows Services
  16. Troubleshooting: Service Application Won't Install


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



Developing Windows Service Applications


Using Microsoft Visual Studio or the Microsoft .NET Framework SDK, you can easily create services by creating an application that is installed as a service. This type of application is called a Windows service. With framework features, you can create services, install them, and start, stop, and otherwise control their behavior.

System_CAPS_ICON_warning.jpg Warning

The Windows service template for C++ was not included in Visual Studio 2010. To create a Windows service, you can either create a service in managed code in Visual C# or Visual Basic, which could interoperate with existing C++ code if required, or you can create a Windows service in native C++ by using the ATL Project Wizard.

Introduction to Windows Service Applications
Provides an overview of Windows service applications, the lifetime of a service, and how service applications differ from other common project types.

Walkthrough: Creating a Windows Service Application in the Component Designer
Provides an example of creating a service in Visual Basic and Visual C#.

Service Application Programming Architecture
Explains the language elements used in service programming.

How to: Create Windows Services
Describes the process of creating and configuring Windows services using the Windows service project template.

ServiceBase
Describes the major features of the ServiceBase class, which is used to create services.

ServiceProcessInstaller
Describes the features of the ServiceProcessInstaller class, which is used along with the ServiceInstaller class to install and uninstall your services.

ServiceInstaller
Describes the features of the ServiceInstaller class, which is used along with the ServiceProcessInstaller class to install and uninstall your service.

NIB Creating Projects from Templates
Describes the projects types used in this chapter and how to choose between them.





Introduction to Windows Service Applications


Microsoft Windows services, formerly known as NT services, enable you to create long-running executable applications that run in their own Windows sessions. These services can be automatically started when the computer boots, can be paused and restarted, and do not show any user interface. These features make services ideal for use on a server or whenever you need long-running functionality that does not interfere with other users who are working on the same computer. You can also run services in the security context of a specific user account that is different from the logged-on user or the default computer account. For more information about services and Windows sessions, see the Windows SDK documentation in the MSDN Library.

You can easily create services by creating an application that is installed as a service. For example, suppose you want to monitor performance counter data and react to threshold values. You could write a Windows Service application that listens to the performance counter data, deploy the application, and begin collecting and analyzing data.

You create your service as a Microsoft Visual Studio project, defining code within it that controls what commands can be sent to the service and what actions should be taken when those commands are received. Commands that can be sent to a service include starting, pausing, resuming, and stopping the service; you can also execute custom commands.

After you create and build the application, you can install it by running the command-line utility InstallUtil.exe and passing the path to the service's executable file. You can then use the Services Control Manager to start, stop, pause, resume, and configure your service. You can also accomplish many of these same tasks in the Services node in Server Explorer or by using the ServiceController class.

Service applications function differently from many other project types in several ways:

  • The compiled executable file that a service application project creates must be installed on the server before the project can function in a meaningful way. You cannot debug or run a service application by pressing F5 or F11; you cannot immediately run a service or step into its code. Instead, you must install and start your service, and then attach a debugger to the service's process. For more information, see How to: Debug Windows Service Applications.

  • Unlike some types of projects, you must create installation components for service applications. The installation components install and register the service on the server and create an entry for your service with the Windows Services Control Manager. For more information, see How to: Add Installers to Your Service Application.

  • The Main method for your service application must issue the Run command for the services your project contains. The Run method loads the services into the Services Control Manager on the appropriate server. If you use the Windows Services project template, this method is written for you automatically. Note that loading a service is not the same thing as starting the service. See "Service Lifetime" below for more information.

  • Windows Service applications run in a different window station than the interactive station of the logged-on user. A window station is a secure object that contains a Clipboard, a set of global atoms, and a group of desktop objects. Because the station of the Windows service is not an interactive station, dialog boxes raised from within a Windows service application will not be seen and may cause your program to stop responding. Similarly, error messages should be logged in the Windows event log rather than raised in the user interface.

    The Windows service classes supported by the .NET Framework do not support interaction with interactive stations, that is, the logged-on user. The .NET Framework also does not include classes that represent stations and desktops. If your Windows service must interact with other stations, you will need to access the unmanaged Windows API. For more information, see the Windows SDK documentation.

    The interaction of the Windows service with the user or other stations must be carefully designed to include scenarios such as there being no logged on user, or the user having an unexpected set of desktop objects. In some cases, it may be more appropriate to write a Windows application that runs under the control of the user.

  • Windows service applications run in their own security context and are started before the user logs into the Windows computer on which they are installed. You should plan carefully what user account to run the service within; a service running under the system account has more permissions and privileges than a user account.

A service goes through several internal states in its lifetime. First, the service is installed onto the system on which it will run. This process executes the installers for the service project and loads the service into the Services Control Manager for that computer. The Services Control Manager is the central utility provided by Windows to administer services.

After the service has been loaded, it must be started. Starting the service allows it to begin functioning. You can start a service from the Services Control Manager, from Server Explorer, or from code by calling the Start method. The Start method passes processing to the application's OnStart method and processes any code you have defined there.

A running service can exist in this state indefinitely until it is either stopped or paused or until the computer shuts down. A service can exist in one of three basic states: RunningPaused, or Stopped. The service can also report the state of a pending command: ContinuePendingPausePendingStartPending, or StopPending. These statuses indicate that a command has been issued, such as a command to pause a running service, but has not been carried out yet. You can query the Status to determine what state a service is in, or use the WaitForStatus to carry out an action when any of these states occurs.

You can pause, stop, or resume a service from the Services Control Manager, from Server Explorer, or by calling methods in code. Each of these actions can call an associated procedure in the service (OnStopOnPause, or OnContinue), in which you can define additional processing to be performed when the service changes state.

There are two types of services you can create in Visual Studio using the .NET Framework. Services that are the only service in a process are assigned the type Win32OwnProcess. Services that share a process with another service are assigned the type Win32ShareProcess. You can retrieve the service type by querying the ServiceType property.

You might occasionally see other service types if you query existing services that were not created in Visual Studio. For more information on these, see the ServiceType.

The ServiceController component is used to connect to an installed service and manipulate its state; using a ServiceController component, you can start and stop a service, pause and continue its functioning, and send custom commands to a service. However, you do not need to use a ServiceController component when you create a service application. In fact, in most cases your ServiceController component should exist in a separate application from the Windows service application that defines your service.

For more information, see ServiceController.

  • Services must be created in a Windows Service application project or another .NET Framework–enabled project that creates an .exe file when built and inherits from the ServiceBase class.

  • Projects containing Windows services must have installation components for the project and its services. This can be easily accomplished from the Properties window. For more information, see How to: Add Installers to Your Service Application.






Walkthrough: Creating a Windows Service Application in the Component Designer


This article demonstrates how to create a simple Windows Service application in Visual Studio that writes messages to an event log. Here are the basic steps that you perform to create and use your service:

  1. Creating a Service by using the Windows Service project template, and configure it. This template creates a class for you that inherits from System.ServiceProcess.ServiceBase and writes much of the basic service code, such as the code to start the service.

  2. Adding Features to the Service for the OnStart and OnStop procedures, and override any other methods that you want to redefine.

  3. Setting Service Status. By default, services created with System.ServiceProcess.ServiceBase implement only a subset of the available status flags. If your service takes a long time to start up, pause, or stop, you can implement status values such as Start Pending or Stop Pending to indicate that it's working on an operation.

  4. Adding Installers to the Service for your service application.

  5. (Optional) Set Startup Parameters, specify default startup arguments, and enable users to override default settings when they start your service manually.

  6. Building the Service.

  7. Installing the Service on the local machine.

  8. Access the Windows Service Control Manager and Starting and Running the Service.

  9. Uninstalling a Windows Service.

System_CAPS_ICON_warning.jpg Warning

The Windows Services project template that is required for this walkthrough is not available in the Express edition of Visual Studio.

System_CAPS_ICON_note.jpg Note

Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Personalizing the IDE.

To begin, you create the project and set values that are required for the service to function correctly.

To create and configure your service

  1. In Visual Studio, on the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  2. In the list of Visual Basic or Visual C# project templates, choose Windows Service, and name the project MyNewService. Choose OK.

    The project template automatically adds a component class named Service1 that inherits from System.ServiceProcess.ServiceBase.

  3. On the Edit menu, choose Find and ReplaceFind in Files (Keyboard: Ctrl+Shift+F). Change all occurrences of Service1 to MyNewService. You’ll find instances in Service1.cs, Program.cs, and Service1.Designer.cs (or their .vb equivalents).

  4. In the Properties window for Service1.cs [Design] or Service1.vb [Design], set the ServiceName and the (Name) property for Service1 to MyNewService, if it's not already set.

  5. In Solution Explorer, rename Service1.cs to MyNewService.cs, or Service1.vb to MyNewService.vb.

In this section, you add a custom event log to the Windows service. Event logs are not associated in any way with Windows services. Here the EventLog component is used as an example of the type of component you could add to a Windows service.

To add custom event log functionality to your service

  1. In Solution Explorer, open the context menu for MyNewService.cs or MyNewService.vb, and then choose View Designer.

  2. From the Components section of the Toolbox, drag an EventLog component to the designer.

  3. In Solution Explorer, open the context menu for MyNewService.cs or MyNewService.vb, and then choose View Code.

  4. Add a declaration for the eventLog object in the MyNewService class, right after the line that declares the components variable:

      private System.ComponentModel.IContainer components;
      private System.Diagnostics.EventLog eventLog1;
    

  5. Add or edit the constructor to define a custom event log:

    	public MyNewService()
    	{
    		InitializeComponent();
                    eventLog1 = new System.Diagnostics.EventLog();
    		if (!System.Diagnostics.EventLog.SourceExists("MySource")) 
    		{         
    				System.Diagnostics.EventLog.CreateEventSource(
    					"MySource","MyNewLog");
    		}
    		eventLog1.Source = "MySource";
    		eventLog1.Log = "MyNewLog";
    	}
    

To define what occurs when the service starts

  • In the Code Editor, locate the OnStart method that was automatically overridden when you created the project, and replace the code with the following. This adds an entry to the event log when the service starts running:

    	protected override void OnStart(string[] args)
    	{
    		eventLog1.WriteEntry("In OnStart");
    	}
    

    A service application is designed to be long-running, so it usually polls or monitors something in the system. The monitoring is set up in the OnStart method. However, OnStart doesn’t actually do the monitoring. The OnStart method must return to the operating system after the service's operation has begun. It must not loop forever or block. To set up a simple polling mechanism, you can use the System.Timers.Timer component as follows: In the OnStart method, set parameters on the component, and then set the Enabled property to true. The timer raises events in your code periodically, at which time your service could do its monitoring. You can use the following code to do this:

    // Set up a timer to trigger every minute.  
    System.Timers.Timer timer = new System.Timers.Timer();  
    timer.Interval = 60000; // 60 seconds  
    timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnTimer);  
    timer.Start();  
    
    

    Add code to handle the timer event:

    public void OnTimer(object sender, System.Timers.ElapsedEventArgs args)  
    {  
        // TODO: Insert monitoring activities here.  
        eventLog1.WriteEntry("Monitoring the System", EventLogEntryType.Information, eventId++);  
    }  
    
    

    You might want to perform tasks by using background worker threads instead of running all your work on the main thread. For an example of this, see the System.ServiceProcess.ServiceBase reference page.

To define what occurs when the service is stopped

  • Replace the code for the OnStop method with the following. This adds an entry to the event log when the service is stopped:

    	protected override void OnStop()
    	{
    		eventLog1.WriteEntry("In onStop.");
    	}
    

In the next section, you can override the OnPauseOnContinue, and OnShutdown methods to define additional processing for your component.

To define other actions for the service

  • Locate the method that you want to handle, and override it to define what you want to occur.

    The following code shows how you can override the OnContinue method:

    	protected override void OnContinue()
    	{
    		eventLog1.WriteEntry("In OnContinue.");
    	}  
    

Some custom actions have to occur when a Windows service is installed by the Installer class. Visual Studio can create these installers specifically for a Windows service and add them to your project.

Services report their status to the Service Control Manager, so that users can tell whether a service is functioning correctly. By default, services that inherit from ServiceBase report a limited set of status settings, including Stopped, Paused, and Running. If a service takes a little while to start up, it might be helpful to report a Start Pending status. You can also implement the Start Pending and Stop Pending status settings by adding code that calls into the Windows SetServiceStatus function.

To implement service pending status

  1. Add a using statement or Imports declaration to the System.Runtime.InteropServices namespace in the MyNewService.cs or MyNewService.vb file:

    using System.Runtime.InteropServices;  
    
    
  2. Add the following code to MyNewService.cs to declare the ServiceState values and to add a structure for the status, which you'll use in a platform invoke call:

    public enum ServiceState  
      {  
          SERVICE_STOPPED = 0x00000001,  
          SERVICE_START_PENDING = 0x00000002,  
          SERVICE_STOP_PENDING = 0x00000003,  
          SERVICE_RUNNING = 0x00000004,  
          SERVICE_CONTINUE_PENDING = 0x00000005,  
          SERVICE_PAUSE_PENDING = 0x00000006,  
          SERVICE_PAUSED = 0x00000007,  
      }  
    
      [StructLayout(LayoutKind.Sequential)]  
      public struct ServiceStatus  
      {  
          public long dwServiceType;  
          public ServiceState dwCurrentState;  
          public long dwControlsAccepted;  
          public long dwWin32ExitCode;  
          public long dwServiceSpecificExitCode;  
          public long dwCheckPoint;  
          public long dwWaitHint;  
      };  
    
    
  3. Now, in the MyNewService class, declare the SetServiceStatus function by using platform invoke:

    [DllImport("advapi32.dll", SetLastError=true)]  
            private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);  
    
    
    
  4. To implement the Start Pending status, add the following code to the beginning of the OnStart method:

    // Update the service state to Start Pending.  
    ServiceStatus serviceStatus = new ServiceStatus();  
    serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;  
    serviceStatus.dwWaitHint = 100000;  
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);  
    
    
  5. Add code to set the status to Running at the end of the OnStart method.

    // Update the service state to Running.  
    serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;  
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);  
    
    
    ' Update the service state to Running.  
    serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING  
    SetServiceStatus(Me.ServiceHandle, serviceStatus)  
    
    
  6. (Optional) Repeat this procedure for the OnStop method.

System_CAPS_ICON_caution.jpg Caution

The Service Control Manager uses the dwWaitHint and dwCheckpoint members of the SERVICE_STATUS structure to determine how much time to wait for a Windows Service to start or shut down. If your OnStart and OnStop methods run long, your service can request more time by calling SetServiceStatus again with an incremented dwCheckPoint value.

Before you can run a Windows Service, you need to install it, which registers it with the Service Control Manager. You can add installers to your project that handle the registration details.

To create the installers for your service

  1. In Solution Explorer, open the context menu for MyNewService.cs or MyNewService.vb, and then choose View Designer.

  2. Click the background of the designer to select the service itself, instead of any of its contents.

  3. Open the context menu for the designer window (if you’re using a pointing device, right-click inside the window), and then choose Add Installer.

    By default, a component class that contains two installers is added to your project. The component is named ProjectInstaller, and the installers it contains are the installer for your service and the installer for the service's associated process.

  4. In Design view for ProjectInstaller, choose serviceInstaller1 for a Visual C# project, or ServiceInstaller1 for a Visual Basic project.

  5. In the Properties window, make sure the ServiceName property is set to MyNewService.

  6. Set the Description property to some text, such as "A sample service". This text appears in the Services window and helps the user identify the service and understand what it’s used for.

  7. Set the DisplayName property to the text that you want to appear in the Services window in the Name column. For example, you can enter "MyNewService Display Name". This name can be different from the ServiceName property, which is the name used by the system (for example, when you use the net start command to start your service).

  8. Set the StartType property to Automatic.

    Installer Properties for a Windows Service

  9. In the designer, choose serviceProcessInstaller1 for a Visual C# project, or ServiceProcessInstaller1 for a Visual Basic project. Set the Account property to LocalSystem. This will cause the service to be installed and to run on a local service account.

    System_CAPS_ICON_important.jpg Important

    The LocalSystem account has broad permissions, including the ability to write to the event log. Use this account with caution, because it might increase your risk of attacks from malicious software. For other tasks, consider using the LocalService account, which acts as a non-privileged user on the local computer and presents anonymous credentials to any remote server. This example fails if you try to use the LocalService account, because it needs permission to write to the event log.

    For more information about installers, see How to: Add Installers to Your Service Application.

A Windows Service, like any other executable, can accept command-line arguments, or startup parameters. When you add code to process startup parameters, users can start your service with their own custom startup parameters by using the Services window in the Windows Control Panel. However, these startup parameters are not persisted the next time the service starts. To set startup parameters permanently, you can set them in the registry, as shown in this procedure.

System_CAPS_ICON_note.jpg Note

Before you decide to add startup parameters, consider whether that is the best way to pass information to your service. Although startup parameters are easy to use and to parse, and users can easily override them, they might be harder for users to discover and use without documentation. Generally, if your service requires more than just a few startup parameters, you should consider using the registry or a configuration file instead. Every Windows Service has an entry in the registry under HKLM\System\CurrentControlSet\services. Under the service's key, you can use the Parameters subkey to store information that your service can access. You can use application configuration files for a Windows Service the same way you do for other types of programs. For example code, see AppSettings.

Adding startup parameters

  1. In the Main method in Program.cs or in MyNewService.Designer.vb, add an argument for the command line:

    static void Main(string[] args)        {            ServiceBase[] ServicesToRun;            ServicesToRun = new ServiceBase[]             {                 new MyNewService(args)             };            ServiceBase.Run(ServicesToRun);        }  
    
    
  2. Change the MyNewService constructor as follows:

    public MyNewService(string[] args)        {            InitializeComponent();            string eventSourceName = "MySource";            string logName = "MyNewLog";            if (args.Count() > 0)            {                eventSourceName = args[0];            }            if (args.Count() > 1)            {                logName = args[1];            }            eventLog1 = new System.Diagnostics.EventLog();            if (!System.Diagnostics.EventLog.SourceExists(eventSourceName))            {                System.Diagnostics.EventLog.CreateEventSource(                    eventSourceName, logName);            }            eventLog1.Source = eventSourceName;            eventLog1.Log = logName;        }  
    
    

    This code sets the event source and log name according to the supplied startup parameters, or uses default values if no arguments are supplied.

  3. To specify the command-line arguments, add the following code to the ProjectInstaller class in ProjectInstaller.cs or ProjectInstaller.vb:

    protected override void OnBeforeInstall(IDictionary savedState)        {            string parameter = "MySource1\" \"MyLogFile1";            Context.Parameters["assemblypath"] = "\"" + Context.Parameters["assemblypath"] + "\" \"" + parameter + "\"";            base.OnBeforeInstall(savedState);        }  
    
    

    This code modifies the ImagePath registry key, which typically contains the full path to the executable for the Windows Service, by adding the default parameter values. The quotation marks around the path (and around each individual parameter) are required for the service to start up correctly. To change the startup parameters for this Windows Service, users can change the parameters given in the ImagePath registry key, although the better way is to change it programmatically and expose the functionality to users in a friendly way (for example, in a management or configuration utility).

To build your service project

  1. In Solution Explorer, open the context menu for your project, and then choose Properties. The property pages for your project appear.

  2. On the Application tab, in the Startup object list, choose MyNewService.Program.

  3. In Solution Explorer, open the context menu for your project, and then choose Build to build the project (Keyboard: Ctrl+Shift+B).

Now that you've built the Windows service, you can install it. To install a Windows service, you must have administrative credentials on the computer on which you're installing it.

To install a Windows Service

  1. In Windows 7 and Windows Server, open the Developer Command Prompt under Visual Studio Tools in the Start menu. In Windows 8 or Windows 8.1, choose the Visual Studio Tools tile on the Start screen, and then run Developer Command Prompt with administrative credentials. (If you’re using a mouse, right-click on Developer Command Prompt, and then choose Run as Administrator.)

  2. In the Command Prompt window, navigate to the folder that contains your project's output. For example, under your My Documents folder, navigate to Visual Studio 2013\Projects\MyNewService\bin\Debug.

  3. Enter the following command:

    installutil.exe MyNewService.exe  
    
    

    If the service installs successfully, installutil.exe will report success. If the system could not find InstallUtil.exe, make sure that it exists on your computer. This tool is installed with the .NET Framework to the folder %WINDIR%\Microsoft.NET\Framework[64]\framework_version. For example, the default path for the 32-bit version of the .NET Framework 4, 4.5, 4.5.1, and 4.5.2 is C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe.

    If the installutil.exe process reports failure, check the install log to find out why. By default the log is in the same folder as the service executable. The installation can fail if the RunInstallerAttribute Class is not present on the ProjectInstaller class, or else the attribute is not set to true, or else the ProjectInstaller class is not public.

    For more information, see How to: Install and Uninstall Services.

To start and stop your service

  1. In Windows, open the Start screen or Start menu, and type services.msc.

    You should now see MyNewService listed in the Services window.

    MyNewService in the Services window.

  2. In the Services window, open the shortcut menu for your service, and then choose Start.

  3. Open the shortcut menu for the service, and then choose Stop.

  4. (Optional) From the command line, you can use the commands net start``ServiceName and net stop``ServiceName to start and stop your service.

To verify the event log output of your service

  1. In Visual Studio, open Server Explorer (Keyboard: Ctrl+Alt+S), and access the Event Logs node for the local computer.

  2. Locate the listing for MyNewLog (or MyLogFile1, if you used the optional procedure to add command-line arguments) and expand it. You should see entries for the two actions (start and stop) your service has performed.

    Use the Event Viewer to see the event log entries.

To uninstall your service

  1. Open a developer command prompt with administrative credentials.

  2. In the Command Prompt window, navigate to the folder that contains your project's output. For example, under your My Documents folder, navigate to Visual Studio 2013\Projects\MyNewService\bin\Debug.

  3. Enter the following command:

    installutil.exe /u MyNewService.exe  
    
    

    If the service uninstalls successfully, installutil.exe will report that your service was successfully removed. For more information, see How to: Install and Uninstall Services.

You can create a standalone setup program that others can use to install your Windows service, but it requires additional steps. ClickOnce doesn't support Windows services, so you can't use the Publish Wizard. You can use a full edition of InstallShield, which Microsoft doesn't provide. For more information about InstallShield, see InstallShield Limited Edition. You can also use the Windows Installer XML Toolset to create an installer for a Windows service.

You might explore the use of a ServiceController component, which enables you to send commands to the service you have installed.

You can use an installer to create an event log when the application is installed instead of creating the event log when the application runs. Additionally, the event log will be deleted by the installer when the application is uninstalled. For more information, see the EventLogInstaller reference page.






Service Application Programming Architecture


Windows Service applications are based on a class that inherits from the System.ServiceProcess.ServiceBase class. You override methods from this class and define functionality for them to determine how your service behaves.

The main classes involved in service creation are:

In addition, a class named ServiceController can be used to manipulate the service itself. This class is not involved in the creation of a service, but can be used to start and stop the service, pass commands to it, and return a series of enumerations.

In your service class, you override base class functions that determine what happens when the state of your service is changed in the Services Control Manager. The ServiceBase class exposes the following methods, which you can override to add custom behavior.

MethodOverride to
OnStartIndicate what actions should be taken when your service starts running. You must write code in this procedure for your service to perform useful work.
OnPauseIndicate what should happen when your service is paused.
OnStopIndicate what should happen when your service stops running.
OnContinueIndicate what should happen when your service resumes normal functioning after being paused.
OnShutdownIndicate what should happen just prior to your system shutting down, if your service is running at that time.
OnCustomCommandIndicate what should happen when your service receives a custom command. For more information on custom commands, see MSDN online.
OnPowerEventIndicate how the service should respond when a power management event is received, such as a low battery or suspended operation.
System_CAPS_ICON_note.jpg Note

These methods represent states that the service moves through in its lifetime; the service transitions from one state to the next. For example, you will never get the service to respond to an OnContinue command before OnStart has been called.

There are several other properties and methods that are of interest. These include:

  • The Run method on the ServiceBase class. This is the main entry point for the service. When you create a service using the Windows Service template, code is inserted in your application's Main method to run the service. This code looks like this:

    		System.ServiceProcess.ServiceBase[] ServicesToRun;
    		ServicesToRun = new System.ServiceProcess.ServiceBase[] 
    		  { new Service1() };
    		System.ServiceProcess.ServiceBase.Run(ServicesToRun);
    

    System_CAPS_ICON_note.jpg Note

    These examples use an array of type ServiceBase, into which each service your application contains can be added, and then all of the services can be run together. If you are only creating a single service, however, you might choose not to use the array and simply declare a new object inheriting from ServiceBase and then run it. For an example, see How to: Write Services Programmatically.

  • A series of properties on the ServiceBase class. These determine what methods can be called on your service. For example, when the CanStop property is set to true, the OnStop method on your service can be called. When the CanPauseAndContinue property is set to true, the OnPause and OnContinue methods can be called. When you set one of these properties to true, you should then override and define processing for the associated methods.

    System_CAPS_ICON_note.jpg Note

    Your service must override at least OnStart and OnStop to be useful.

You can also use a component called the ServiceController to communicate with and control the behavior of an existing service.






How to: Create Windows Services


When you create a service, you can use a Visual Studio project template called Windows Service. This template automatically does much of the work for you by referencing the appropriate classes and namespaces, setting up the inheritance from the base class for services, and overriding several of the methods you're likely to want to override.

System_CAPS_ICON_warning.jpg Warning

The Windows Services project template is not available in the Express edition of Visual Studio.

At a minimum, to create a functional service you must:

  • Set the ServiceName property.

  • Create the necessary installers for your service application.

  • Override and specify code for the OnStart and OnStop methods to customize the ways in which your service behaves.

To create a Windows Service application

  1. Create a Windows Service project.

    System_CAPS_ICON_note.jpg Note

    For instructions on writing a service without using the template, see How to: Write Services Programmatically.

  2. In the Properties window, set the ServiceName property for your service.

    Set the ServiceName property.

    System_CAPS_ICON_note.jpg Note

    The value of the ServiceName property must always match the name recorded in the installer classes. If you change this property, you must update the ServiceName property of installer classes as well.

  3. Set any of the following properties to determine how your service will function.

    PropertySetting
    CanStopTrue to indicate that the service will accept requests to stop running; false to prevent the service from being stopped.
    CanShutdownTrue to indicate that the service wants to receive notification when the computer on which it lives shuts down, enabling it to call the OnShutdown procedure.
    CanPauseAndContinueTrue to indicate that the service will accept requests to pause or to resume running; false to prevent the service from being paused and resumed.
    CanHandlePowerEventTrue to indicate that the service can handle notification of changes to the computer's power status; false to prevent the service from being notified of these changes.
    AutoLogTrue to write informational entries to the Application event log when your service performs an action; false to disable this functionality. For more information, see How to: Log Information About ServicesNote: By default, AutoLog is set to true.
    System_CAPS_ICON_note.jpg Note

    When CanStopor CanPauseAndContinueare set to false, the Service Control Manager will disable the corresponding menu options to stop, pause, or continue the service.

  4. Access the Code Editor and fill in the processing you want for the OnStart and OnStop procedures.

  5. Override any other methods for which you want to define functionality.

  6. Add the necessary installers for your service application. For more information, see How to: Add Installers to Your Service Application.

  7. Build your project by selecting Build Solution from the Build menu.

    System_CAPS_ICON_note.jpg Note

    Do not press F5 to run your project — you cannot run a service project in this way.

  8. Install the service. For more information, see How to: Install and Uninstall Services.






How to: Write Services Programmatically


If you choose not to use the Windows Service project template, you can write your own services by setting up the inheritance and other infrastructure elements yourself. When you create a service programmatically, you must perform several steps that the template would otherwise handle for you:

  • You must set up your service class to inherit from the ServiceBase class.

  • You must create a Main method for your service project that defines the services to run and calls the Run method on them.

  • You must override the OnStart and OnStop procedures and fill in any code you want them to run.

To write a service programmatically

  1. Create an empty project and create a reference to the necessary namespaces by following these steps:

    1. In Solution Explorer, right-click the References node and click Add Reference.

    2. On the .NET Framework tab, scroll to System.dll and click Select.

    3. Scroll to System.ServiceProcess.dll and click Select.

    4. Click OK.

  2. Add a class and configure it to inherit from ServiceBase:

    public class UserService1 : System.ServiceProcess.ServiceBase  
    {
    }
    

  3. Add the following code to configure your service class:

    	public UserService1() 
    	{
    		this.ServiceName = "MyService2";
    		this.CanStop = true;
    		this.CanPauseAndContinue = true;
    		this.AutoLog = true;
    	}
    

  4. Create a Main method for your class, and use it to define the service your class will contain; userService1 is the name of the class:

    	public static void Main()
    	{
    		System.ServiceProcess.ServiceBase.Run(new UserService1());
    	}
    

  5. Override the OnStart method, and define any processing you want to occur when your service is started.

    	protected override void OnStart(string[] args)
    	{
    		// Insert code here to define processing.
    	}
    

  6. Override any other methods you want to define custom processing for, and write code to determine the actions the service should take in each case.

  7. Add the necessary installers for your service application. For more information, see How to: Add Installers to Your Service Application.

  8. Build your project by selecting Build Solution from the Build menu.

    System_CAPS_ICON_note.jpg Note

    Do not press F5 to run your project — you cannot run a service project in this way.

  9. Create a setup project and the custom actions to install your service. For an example, see Walkthrough: Creating a Windows Service Application in the Component Designer.

  10. Install the service. For more information, see How to: Install and Uninstall Services.


# EventLog를 수동으로 만들 때 Source와 Log 속성을 지정하지 않으면 서비스 시작시 오류가 발생한다.

EventLog eventLog1 = new EventLog();
eventLog1.Source = "MySource";
eventLog1.Log = "MyNewLog";






How to: Add Installers to Your Service Application


Visual Studio ships installation components that can install resources associated with your service applications. Installation components register an individual service on the system to which it is being installed and let the Services Control Manager know that the service exists. When you work with a service application, you can select a link in the Properties window to automatically add the appropriate installers to your project.

System_CAPS_ICON_note.jpg Note

Property values for your service are copied from the service class to the installer class. If you update the property values on the service class, they are not automatically updated in the installer.

When you add an installer to your project, a new class (which, by default, is named ProjectInstaller) is created in the project, and instances of the appropriate installation components are created within it. This class acts as a central point for all of the installation components your project needs. For example, if you add a second service to your application and click the Add Installer link, a second installer class is not created; instead, the necessary additional installation component for the second service is added to the existing class.

You do not need to do any special coding within the installers to make your services install correctly. However, you may occasionally need to modify the contents of the installers if you need to add special functionality to the installation process.

System_CAPS_ICON_note.jpg Note

The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Customizing Development Settings in Visual Studio.

To add installers to your service application

  1. In Solution Explorer, access Design view for the service for which you want to add an installation component.

  2. Click the background of the designer to select the service itself, rather than any of its contents.

  3. With the designer in focus, right-click, and then click Add Installer.

    A new class, ProjectInstaller, and two installation components, ServiceProcessInstaller and ServiceInstaller, are added to your project, and property values for the service are copied to the components.

  4. Click the ServiceInstaller component and verify that the value of the ServiceName property is set to the same value as the ServiceName property on the service itself.

  5. To determine how your service will be started, click the ServiceInstaller component and set the StartType property to the appropriate value.

    ValueResult
    ManualThe service must be manually started after installation. For more information, see How to: Start Services.
    AutomaticThe service will start by itself whenever the computer reboots.
    DisabledThe service cannot be started.
  6. To determine the security context in which your service will run, click the ServiceProcessInstaller component and set the appropriate property values. For more information, see How to: Specify the Security Context for Services.

  7. Override any methods for which you need to perform custom processing.

  8. Perform steps 1 through 7 for each additional service in your project.

    System_CAPS_ICON_note.jpg Note

    For each additional service in your project, you must add an additional ServiceInstaller component to the project's ProjectInstaller class. The ServiceProcessInstaller component added in step three works with all of the individual service installers in the project.






How to: Specify the Security Context for Services


By default, services run in a different security context than that of the logged-in user. Services run in the context of the default system account, called LocalSystem, which gives them different access privileges to system resources than the user. You can change this behavior to specify a different user account under which your service should run.

You set the security context by manipulating the Account property for the process within which the service runs. This property allows you to set the service to one of four account types:

  • User, which causes the system to prompt for a valid user name and password when the service is installed and runs in the context of an account specified by a single user on the network;

  • LocalService, which runs in the context of an account that acts as a non-privileged user on the local computer, and presents anonymous credentials to any remote server;

  • LocalSystem, which runs in the context of an account that provides extensive local privileges, and presents the computer's credentials to any remote server;

  • NetworkService, which runs in the context of an account that acts as a non-privileged user on the local computer, and presents the computer's credentials to any remote server.

For more information, see the ServiceAccount enumeration.

To specify the security context for a service

  1. After creating your service, add the necessary installers for it. For more information, see How to: Add Installers to Your Service Application.

  2. In the designer, access the ProjectInstaller class and click the service process installer for the service you are working with.

    System_CAPS_ICON_note.jpg Note

    For every service application, there are at least two installation components in the ProjectInstaller class — one that installs the processes for all services in the project, and one installer for each service the application contains. In this instance, you want to select ServiceProcessInstaller.

  3. In the Properties window, set the Account to the appropriate value.







How to: Install and Uninstall Services


If you’re developing a Windows Service by using the .NET Framework, you can quickly install your service application by using a command-line utility called InstallUtil.exe. If you’re a developer who wants to release a Windows Service that users can install and uninstall you should use InstallShield. See Windows Installer Deployment.

System_CAPS_ICON_warning.jpg Warning

If you want to uninstall a service from your computer, don’t follow the steps in this article. Instead, find out which program or software package installed the service, and then choose Add/Remove Programs in Control Panel to uninstall that program. Note that many services are integral parts of Windows; if you remove them, you might cause system instability.

In order to use the steps in this article, you first need to add a service installer to your Windows Service. See Walkthrough: Creating a Windows Service Application in the Component Designer.

Windows Service projects cannot be run directly from the Visual Studio development environment by pressing F5. This is because the service in the project must be installed before you can run the project.

System_CAPS_ICON_tip.jpg Tip

You can launch Server Explorer and verify that your service has been installed or uninstalled. For more information, see How to: Access and Initialize Server Explorer-Database Explorer.

To install your service manually

  1. On the Windows Start menu or Start screen, choose Visual Studio , Visual Studio ToolsDeveloper Command Prompt.

    A Visual Studio command prompt appears.

  2. Access the directory where your project's compiled executable file is located.

  3. Run InstallUtil.exe from the command prompt with your project's executable as a parameter:

    installutil <yourproject>.exe  
    
    

    If you’re using the Visual Studio command prompt, InstallUtil.exe should be on the system path. If not, you can add it to the path, or use the fully qualified path to invoke it. This tool is installed with the .NET Framework, and its path is %WINDIR%\Microsoft.NET\Framework[64]\<framework_version>. For example, for the 32-bit version of the .NET Framework 4 or 4.5.*, if your Windows installation directory is C:\Windows, the path is C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe. For the 64-bit version of the .NET Framework 4 or 4.5.*, the default path is C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe.

To uninstall your service manually

  1. On the Windows Start menu or Start screen, choose Visual StudioVisual Studio ToolsDeveloper Command Prompt.

    A Visual Studio command prompt appears.

  2. Run InstallUtil.exe from the command prompt with your project's output as a parameter:

    installutil /u <yourproject>.exe  
    
    
  3. Sometimes, after the executable for a service is deleted, the service might still be present in the registry. In that case, use the command sc delete to remove the entry for the service from the registry.







How to: Start Services


After a service is installed, it must be started. Starting calls the OnStart method on the service class. Usually, the OnStart method defines the useful work the service will perform. After a service starts, it remains active until it is manually paused or stopped.

Services can be set up to start automatically or manually. A service that starts automatically will be started when the computer on which it is installed is rebooted or first turned on. A user must start a service that starts manually.

System_CAPS_ICON_note.jpg Note

By default, services created with Visual Studio are set to start manually.

There are several ways you can manually start a service — from Server Explorer, from the Services Control Manager, or from code using a component called the ServiceController.

You set the StartType property on the ServiceInstaller class to determine whether a service should be started manually or automatically.

To specify how a service should start

  1. After creating your service, add the necessary installers for it. For more information, see How to: Add Installers to Your Service Application.

  2. In the designer, click the service installer for the service you are working with.

  3. In the Properties window, set the StartType property to one of the following:

    To have your service installSet this value
    When the computer is restartedAutomatic
    When an explicit user action starts the serviceManual
    System_CAPS_ICON_tip.jpg Tip

    To prevent your service from being started at all, you can set the StartType property to Disabled. You might do this if you are going to reboot a server several times and want to save time by preventing the services that would normally start from starting up.

    System_CAPS_ICON_note.jpg Note

    These and other properties can be changed after your service is installed.

    There are several ways you can start a service that has its StartType process set to Manual — from Server Explorer, from the Windows Services Control Manager, or from code. It is important to note that not all of these methods actually start the service in the context of the Services Control ManagerServer Explorer and programmatic methods of starting the service actually manipulate the controller.

To manually start a service from Server Explorer

  1. In Server Explorer, add the server you want if it is not already listed. For more information, see How to: Access and Initialize Server Explorer-Database Explorer.

  2. Expand the Services node, and then locate the service you want to start.

  3. Right-click the name of the service, and click Start.

To manually start a service from Services Control Manager

  1. Open the Services Control Manager by doing one of the following:

    • In Windows XP and 2000 Professional, right-click My Computer on the desktop, and then click Manage. In the dialog box that appears, expand the Services and Applications node.

      - or -

    • In Windows Server 2003 and Windows 2000 Server, click Start, point to Programs, click Administrative Tools, and then click Services.

      System_CAPS_ICON_note.jpg Note

      In Windows NT version 4.0, you can open this dialog box from Control Panel.

    You should now see your service listed in the Services section of the window.

  2. Select your service in the list, right-click it, and then click Start.

To manually start a service from code

  1. Create an instance of the ServiceController class, and configure it to interact with the service you want to administer.

  2. Call the Start method to start the service.







How to: Pause a Windows Service (Visual Basic)


This example uses the ServiceController component to pause the IIS Admin service on the local computer.

    Dim theController As System.ServiceProcess.ServiceController
    theController = New System.ServiceProcess.ServiceController("IISAdmin")


    ' Pauses the service.
    theController.Pause()

This code example is also available as an IntelliSense code snippet. In the code snippet picker, it is located in Windows Operating System > Windows Services. For more information, see Code Snippets.

This example requires:

The MachineName property of the ServiceController class is the local computer by default. To reference Windows services on another computer, change the MachineName property to the name of that computer.

The following conditions may cause an exception:

  • The service cannot be paused. (InvalidOperationException Class)

  • An error occurred when accessing a system API. (Win32Exception Class)

Control of services on the computer may be restricted by using the ServiceControllerPermissionAccess Enumeration to set permissions in the ServiceControllerPermission Class.

Access to service information may be restricted by using the PermissionState Enumeration to set permissions in the SecurityPermission Class.







How to: Continue a Windows Service (Visual Basic)


This example uses the ServiceController component to continue the IIS Admin service on the local computer.

    Dim theController As System.ServiceProcess.ServiceController
    theController = New System.ServiceProcess.ServiceController("IISAdmin")


    ' Checks that the service is paused.
    If theController.Status =
      System.ServiceProcess.ServiceControllerStatus.Paused Then

      ' Continues the service.
      theController.Continue()
    End If

This code example is also available as an IntelliSense code snippet. In the code snippet picker, it is located in Windows Operating System > Windows Services. For more information, see Code Snippets.

This example requires:

The MachineName property of the ServiceController class is the local computer by default. To reference Windows services on another computer, change the MachineName property to the name of that computer.

You cannot call the Continue method on a service until the service controller status is Paused.

The following conditions may cause an exception:

Control of services on the computer may be restricted by using the ServiceControllerPermissionAccess enumeration to set permissions in the ServiceControllerPermission class.

Access to service information may be restricted by using the PermissionState enumeration to set permissions in the SecurityPermission class.







How to: Debug Windows Service Applications


A service must be run from within the context of the Services Control Manager rather than from within Visual Studio. For this reason, debugging a service is not as straightforward as debugging other Visual Studio application types. To debug a service, you must start the service and then attach a debugger to the process in which it is running. You can then debug your application by using all of the standard debugging functionality of Visual Studio.

System_CAPS_ICON_caution.jpg Caution

You should not attach to a process unless you know what the process is and understand the consequences of attaching to and possibly killing that process. For example, if you attach to the WinLogon process and then stop debugging, the system will halt because it can’t operate without WinLogon.

You can attach the debugger only to a running service. The attachment process interrupts the current functioning of your service; it doesn’t actually stop or pause the service's processing. That is, if your service is running when you begin debugging, it is still technically in the Started state as you debug it, but its processing has been suspended.

After attaching to the process, you can set breakpoints and use these to debug your code. Once you exit the dialog box you use to attach to the process, you are effectively in debug mode. You can use the Services Control Manager to start, stop, pause and continue your service, thus hitting the breakpoints you've set. You can later remove this dummy service after debugging is successful.

This article covers debugging a service that's running on the local computer, but you can also debug Windows Services that are running on a remote computer. See Remote Debugging.

System_CAPS_ICON_note.jpg Note

Debugging the OnStart method can be difficult because the Services Control Manager imposes a 30-second limit on all attempts to start a service. For more information, see Troubleshooting: Debugging Windows Services.

System_CAPS_ICON_warning.jpg Warning

To get meaningful information for debugging, the Visual Studio debugger needs to find symbol files for the binaries that are being debugged. If you are debugging a service that you built in Visual Studio, the symbol files (.pdb files) are in the same folder as the executable or library, and the debugger loads them automatically. If you are debugging a service that you didn't build, you should first find symbols for the service and make sure they can be found by the debugger. See Specify Symbol (.pdb) and Source Files. If you're debugging a system process or want to have symbols for system calls in your services, you should add the Microsoft Symbol Servers. See Debugging Symbols.

To debug a service

  1. Build your service in the Debug configuration.

  2. Install your service. For more information, see How to: Install and Uninstall Services.

  3. Start your service, either from Services Control ManagerServer Explorer, or from code. For more information, see How to: Start Services.

  4. Start Visual Studio with administrative credentials so you can attach to system processes.

  5. (Optional) On the Visual Studio menu bar, choose ToolsOptions. In the Options dialog box, choose DebuggingSymbols, select the Microsoft Symbol Servers check box, and then choose the OK button.

  6. On the menu bar, choose Attach to Process from the Debug or Tools menu. (Keyboard: Ctrl+Alt+P)

    The Processes dialog box appears.

  7. Select the Show processes from all users check box.

  8. In the Available Processes section, choose the process for your service, and then choose Attach.

    System_CAPS_ICON_tip.jpg Tip

    The process will have the same name as the executable file for your service.

    The Attach to Process dialog box appears.

  9. Choose the appropriate options, and then choose OK to close the dialog box.

    System_CAPS_ICON_note.jpg Note

    You are now in debug mode.

  10. Set any breakpoints you want to use in your code.

  11. Access the Services Control Manager and manipulate your service, sending stop, pause, and continue commands to hit your breakpoints. For more information about running the Services Control Manager, see How to: Start Services. Also, see Troubleshooting: Debugging Windows Services.

Attaching to the service's process allows you to debug most, but not all, the code for that service. For example, because the service has already been started, you cannot debug the code in the service's OnStart method or the code in the Main method that is used to load the service this way. One way to work around this limitation is to create a temporary second service in your service application that exists only to aid in debugging. You can install both services, and then start this dummy service to load the service process. Once the temporary service has started the process, you can use the Debug menu in Visual Studio to attach to the service process.

Try adding calls to the Sleep method to delay action until you’re able to attach to the process.

Try changing the program to a regular console application. To do this, rewrite the Main method as follows so it can run both as a Windows Service and as a console application, depending on how it's started.

How to: Run a Windows Service as a console application

  1. Add a method to your service that runs the OnStart and OnStop methods:

    internal void TestStartupAndStop(string[] args)  
    {  
        this.OnStart(args);  
        Console.ReadLine();  
        this.OnStop();  
    }  
    
    
  2. Rewrite the Main method as follows:

    static void Main(string[] args)  
            {  
                if (Environment.UserInteractive)  
                {  
                    MyNewService service1 = new MyNewService(args);  
                    service1.TestStartupAndStop(args);  
                }  
                else  
                {  
                    // Put the body of your old Main method here.  
                }  
    
    
  3. In the Application tab of the project's properties, set the Output type to Console Application.

  4. Choose Start Debugging (F5).

  5. To run the program as a Windows Service again, install it and start it as usual for a Windows Service. It's not necessary to reverse these changes.

In some cases, such as when you want to debug an issue that occurs only on system startup, you have to use the Windows debugger. Install Debugging Tools for Windows and see How to debug Windows Services.







How to: Log Information About Services


By default, all Windows Service projects have the ability to interact with the Application event log and write information and exceptions to it. You use the AutoLog property to indicate whether you want this functionality in your application. By default, logging is turned on for any service you create with the Windows Service project template. You can use a static form of the EventLog class to write service information to a log without having to create an instance of an EventLog component or manually register a source.

The installer for your service automatically registers each service in your project as a valid source of events with the Application log on the computer where the service is installed, when logging is turned on. The service logs information each time the service is started, stopped, paused, resumed, installed, or uninstalled. It also logs any failures that occur. You do not need to write any code to write entries to the log when using the default behavior; the service handles this for you automatically.

If you want to write to an event log other than the Application log, you must set the AutoLog property to false, create your own custom event log within your services code, and register your service as a valid source of entries for that log. You must then write code to record entries to the log whenever an action you're interested in occurs.

System_CAPS_ICON_note.jpg Note

If you use a custom event log and configure your service application to write to it, you must not attempt to access the event log before setting the service's ServiceName property in your code. The event log needs this property's value to register your service as a valid source of events.

To enable default event logging for your service

  • Set the AutoLog property for your component to true.

    System_CAPS_ICON_note.jpg Note

    By default, this property is set to true. You do not need to set this explicitly unless you are building more complex processing, such as evaluating a condition and then setting the AutoLog property based on the result of that condition.

To disable event logging for your service

  • Set the AutoLog property for your component to false.

    		this.AutoLog = false;
    

To set up logging to a custom log

  1. Set the AutoLog property to false.

    System_CAPS_ICON_note.jpg Note

    You must set AutoLog to false in order to use a custom log.

  2. Set up an instance of an EventLog component in your Windows Service application.

  3. Create a custom log by calling the CreateEventSource method and specifying the source string and the name of the log file you want to create.

  4. Set the Source property on the EventLog component instance to the source string you created in step 3.

  5. Write your entries by accessing the WriteEntry method on the EventLog component instance.

    The following code shows how to set up logging to a custom log.

    System_CAPS_ICON_note.jpg Note

    In this code example, an instance of an EventLog component is named eventLog1 (EventLog1 in Visual Basic). If you created an instance with another name in step 2, change the code accordingly.

    	public UserService2()
    	{
                    eventLog1 = new System.Diagnostics.EventLog();
    		// Turn off autologging
    		this.AutoLog = false;
    		// create an event source, specifying the name of a log that
    		// does not currently exist to create a new, custom log
    		if (!System.Diagnostics.EventLog.SourceExists("MySource")) 
    		{        
    				System.Diagnostics.EventLog.CreateEventSource(
    					"MySource","MyLog");
    		}
    		// configure the event log instance to use this source name
    		eventLog1.Source = "MySource";
                    eventLog1.Log = "MyLog";
    	}
    

    	protected override void OnStart(string[] args)
    	{
    		// write an entry to the log
    		eventLog1.WriteEntry("In OnStart.");
    	}







Troubleshooting: Debugging Windows Services


When you debug a Windows service application, your service and the Windows Service Manager interact. The Service Manager starts your service by calling the OnStart method, and then waits 30 seconds for the OnStart method to return. If the method does not return in this time, the manager shows an error that the service cannot be started.

When you debug the OnStart method as described in How to: Debug Windows Service Applications, you must be aware of this 30-second period. If you place a breakpoint in the OnStart method and do not step through it in 30 seconds, the manager will not start the service.






Troubleshooting: Service Application Won't Install


If your service application will not install correctly, check to make sure that the ServiceName property for the service class is set to the same value as is shown in the installer for that service. The value must be the same in both instances in order for your service to install correctly.

System_CAPS_ICON_note.jpg Note

You can also look at the installation logs to get feedback on the installation process.

You should also check to determine whether you have another service with the same name already installed. Service names must be unique for installation to succeed.







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

Task-based Asynchronous Programming  (0) 2017.04.22
Asynchronous Programming with async and await (C#)  (0) 2017.04.19
Events  (0) 2017.03.29
Common I/O Tasks  (0) 2017.03.15
Thread 관련  (0) 2017.03.05
:
Posted by 지훈2