C# task based asynchronous programming

Posted by vidhu on Sat, 12 Feb 2022 00:04:25 +0100

The task parallel library (TPL) is based on the concept of "task", which represents asynchronous operation. In some ways, tasks are similar to threads or ThreadPool Work items, but at a higher level of abstraction. The term "task parallelism" refers to one or more independent tasks running simultaneously. Tasks provide two main benefits:

  • The use efficiency of system resources is higher and the scalability is better.

    In the background, tasks are queued to an algorithm enhanced ThreadPool , these algorithms can determine the number of threads and adjust them accordingly, providing load balancing to maximize throughput. This makes the task relatively light, and you can create many tasks to enable refinement parallelism.

  • For threads or work items, you can use more programmatic controls.

    Tasks and the framework generated around them provide a rich set of API s that support wait, cancel, continue, reliable exception handling, detailed status, custom plan and other functions.

For these two reasons, in NET, TPL is the preferred API for writing multithreaded, asynchronous and parallel code.

Implicitly create and run tasks

Parallel.Invoke Method provides a simple way to run any number of arbitrary statements at the same time. Just pass in for each work item Action Just entrust. The easiest way to create these delegates is to use lambda expressions. Lambda expressions can call specified methods or provide inline code. The following example demonstrates a basic Invoke Call, which creates and starts two tasks running simultaneously. The first task is represented by a lambda expression calling a method named , DoSomeWork , and the second task is represented by a lambda expression calling a method named , DoSomeOtherWork ,.

Remarks

This document 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.

C # replication

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

Remarks

Task Created in the background Invoke The number of instances is not necessarily equal to the number of delegates provided. TPL may use various optimizations, especially for a large number of delegates.

For more information, see How to: use parallel Invoke to perform parallel operations.

In order to better control task execution or return values from tasks, it must be used more explicitly Task Object.

Explicitly create and run tasks

Tasks that do not return values are System.Threading.Tasks.Task Class representation. The task that returns the value is determined by System.Threading.Tasks.Task<TResult> Class represents the class from Task Succession. The task object handles infrastructure details and provides methods and properties that can be accessed from the calling thread throughout the lifetime of the task. For example, you can access the of a task at any time Status Property to determine whether it started running, completed running, canceled, or threw an exception. Status by TaskStatus Enumeration representation.

When creating a task, you give it a user delegate that encapsulates the code that the task will execute. The delegate can be represented as a named delegate, an anonymous method, or a lambda expression. Lambda expressions can contain calls to named methods, as shown in the following example. Note that this example includes a pair of Task.Wait Method to ensure that the task is completed before the end of the console mode application.

C # replication

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 Task.Run Method creates and starts a task through an operation. No matter which task scheduler is associated with the current thread, Run Methods will use the default task scheduler to manage tasks. It is preferred when there is no need for more control over the creation and scheduling of tasks Run Method to create and start a task.

C # replication

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 TaskFactory.StartNew Method creates and starts a task in one operation. When you do not have to separate creation from planning and need other task creation options or use a specific scheduler, or need to pass other states to Task.AsyncState Use this method when retrieving the task from the property, as shown in the following example.

C # replication

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.

Task And Task<TResult> All public static Factory Property that returns TaskFactory So you can call this method as task Factory. StartNew(). In addition, in the following example, because the type of task is System.Threading.Tasks.Task<TResult> Therefore, each task has a common task that contains the calculation results Task<TResult>.Result Properties. Tasks run asynchronously and can be completed in any order. If accessed before the calculation is complete Result Property, the property will block the calling thread until the value is available.

C # replication

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 create a delegate using a lambda expression, you have access to all variables visible in the source code at that time. However, in some cases, especially in loops, lambda does not capture variables as expected. It captures only the final value, not the value it changes after each iteration. The following example illustrates this problem. It passes the loop counter to the instantiated CustomData object and uses the loop counter as a lambda expression for the object identifier. As the sample output shows, each custom data object has the same identifier.

C # replication

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.

This value can be accessed at each iteration by providing a state object to the task using a constructor. The following example is modified from the previous example to use a loop counter when creating a custom data object, which is then passed to a lambda expression. As shown in the example output, each custom data object now has a unique identifier based on the value of the loop counter when the object is instantiated.

C # replication

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 to the task delegate as a parameter and can be passed by using Task.AsyncState Property is accessed from the task object. The following example evolves from the previous example. It uses AsyncState Property displays information about the custom data object passed to the lambda expression.

C # replication

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.

Task ID

Each task gets an integer ID that uniquely identifies itself in the application domain, which can be used Task.Id Property to access the ID. This ID is valid for viewing task information in the parallel stack and tasks windows of the Visual Studio debugger. The ID is created lazily, which means that it will not be created before being requested; Therefore, each time you run the program, the task may have a different ID. For details on how to view task IDS in the debugger, see Using the task window and Using parallel stack windows.

Task creation options

Most API s for creating tasks provide acceptance TaskCreationOptions Overload of parameters. By specifying one or more of the following options, you can indicate how the task scheduler schedules tasks in the process pool. You can use the bitwise OR operation combination option.

The following example demonstrates an example with LongRunning And PreferFairness Option.

C # replication

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

Tasks, threads, and cultures

Each thread has an associated culture and UI culture, respectively Thread.CurrentCulture And Thread.CurrentUICulture Attribute definition. Thread culture is used in operations such as formatting, parsing, sorting, and string comparison. The UI culture of the thread is used to find resources.

Unless used CultureInfo.DefaultThreadCurrentCulture And CultureInfo.DefaultThreadCurrentUICulture Property specifies the default culture for all threads in the application domain, and the default culture and UI culture of threads are defined by the system culture. If you explicitly set the culture of a thread and start a new thread, the new thread will not inherit the culture of the calling thread; On the contrary, its culture is the default system culture. However, in Task-based programming, tasks use the culture of the calling thread, even if the task runs asynchronously on different threads.

The following example provides a simple demonstration. It changes the current culture of the application to French (France); Or, if French (France) is already the current culture, change it to English (United States). Then, a delegate named formatDelegate is called to return the number formatted to currency value in the new domain. Whether the delegate is invoked synchronously or asynchronously by the task, the task will use the culture of the calling thread.

C # replication

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

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 €

vbnet replication

Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        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" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' 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:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' 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 Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Remarks

Yes NET Framework before 4.6 In the. NET Framework version, the culture of a task is determined by the culture of the thread on which it runs, not the culture of the calling thread. For asynchronous tasks, this means that the culture used by the task may be different from that of the calling thread.

For more information about asynchronous tasks and cultures, see CultureInfo The "culture and asynchronous task based operations" section of the topic.

Create task continuation

Use Task.ContinueWith And Task<TResult>.ContinueWith Method to specify the task to start when the predecessor task completes. The delegate of the continuation task has passed a reference to the predecessor task, so it can check the status of the predecessor task and retrieve it Task<TResult>.Result The value of the property uses the output of the predecessor task as the input of the continuation task.

In the following example, the getData} task is called TaskFactory.StartNew<TResult>(Func<TResult>) Method to start. When "processData" is completed, the getData "task starts automatically. When" displayData "is completed, processData" starts. getData , generates an integer array. Through the , getData , attribute of the , processData , task, Task<TResult>.Result Task can access the array. The processData task processes the array and returns a result of the type passed from to Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) Method is inferred from the return type of the Lambda expression of the. When displayData , is completed, the processData , task is automatically executed, and Tuple<T1,T2,T3> The task can access the data returned by the # processData # lambda expression through the # displayData # attribute of the # processData # task Task<TResult>.Result Object. The displayData task uses the results of the processData task and then obtains its own results. Its type is inferred in a similar way and can be determined by the results in the program Result Property use.

C # replication

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, so you can link method calls together instead of instantiating for each antecedent Task<TResult> Object. The following example is functionally equivalent to the previous example, except that it will Task.ContinueWith Method calls are linked together. Notice that the value returned through the method call chain Task<TResult> The object is the final continuation task.

C # replication

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

Use ContinueWhenAll And ContinueWhenAny Method, you can continue from multiple tasks.

For more information, see Link tasks using continuation tasks.

Create separate subtasks

If a new task is not created by the user and is not running in the specified code AttachedToParent Option, the new task will not synchronize with the parent task in any special way. This type of asynchronous task is called "separated nested task" or "separated subtask". The following example shows the task of creating a sub ion task.

C # replication

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 sub ion task to complete.

Create subtasks

If the user code running in the task is specified when the task is created AttachedToParent Option, the new task is called the additional subtask of the parent task. Because the parent task implicitly waits for all additional subtasks to complete, you can use AttachedToParent Options represent structured task parallelism. The following example shows a parent task that creates ten additional subtasks. Note that although this example calls Task.Wait Method waits for the parent task to complete, but does not have to explicitly wait for additional subtasks to complete.

C # replication

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.

Parent task available TaskCreationOptions.DenyChildAttach Option prevents other tasks from being attached to the parent task. For more information, see Additional and separate subtasks.

Wait for the task to complete

System.Threading.Tasks.Task And System.Threading.Tasks.Task<TResult> Type provides Task.Wait Method so that you can wait for the task to complete. In addition, use static Task.WaitAll And Task.WaitAny Method overloading can wait for any or all of a batch of tasks to complete.

Typically, you wait for a task for one of the following reasons:

  • The main thread depends on the final result of task calculation.

  • You must handle exceptions that may be thrown from the task.

  • The application can terminate before all tasks are completed. For example, the console application terminates immediately after executing all synchronization codes in the application entry point.

The following example demonstrates a basic pattern that does not include exception handling.

C # replication

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 demonstrates exception handling, see exception handling.

Some overloads allow you to specify a timeout, while others use additional CancellationToken As an input parameter, so that the waiting can be cancelled by programming or according to user input.

When waiting for a task, you are actually implicitly waiting for use TaskCreationOptions.AttachedToParent Option to create all children of the task.   Task.Wait Return immediately when the task has been completed.   Task.Wait Method will throw any exception thrown by a task, even if Task.Wait The method is called after the task is completed.

Combined task

Task Class sum Task<TResult> Class provides a variety of methods that can help you combine multiple tasks to implement common patterns and make better use of the asynchronous language functions provided by C #, Visual Basic and F #. This section describes WhenAll,WhenAny,Delay And FromResult Method.

Task.WhenAll

Task.WhenAll Method asynchronously waits for multiple Task Or Task<TResult> Object complete. The overloaded version provided by it can wait for non-uniform task groups. For example, you can wait multiple times Task And Task<TResult> Object is completed in a method call.

Task.WhenAny

Task.WhenAny Method asynchronously waits for multiple Task Or Task<TResult> One of the objects is complete. With in Task.WhenAll Method, which provides an overloaded version that allows you to wait for non-uniform task groups.   WhenAny Methods are particularly useful in the following situations.

  • Redundant operation. Consider algorithms or operations that can be performed in many ways. You can use WhenAny Method to select the operation completed first, and then cancel the remaining operations.

  • Cross operation. You can start multiple operations that must be completed and use WhenAny Method processes the result when each operation is completed. After an operation is completed, one or more other tasks can be started.

  • Restricted operations. You can use WhenAny Method expands the previous situation by limiting the number of concurrent operations.

  • Expired operations. You can use WhenAny Method a task that is completed after one or more tasks and a specific time (e.g Delay Method). The following section describes Delay Method.

Task.Delay

Task.Delay Method will generate the completed after the specified time Task Object. You can use this method to generate a cycle of occasional polling data, introduce timeouts, delay the processing of user input for a predetermined period of time, etc.

Task(T).FromResult

By using Task.FromResult Method, you can create a file that contains the pre calculated results Task<TResult> Object. Execute return Task<TResult> Object, and the Task<TResult> Object, this method is useful. About use FromResult For an example of retrieving the asynchronous download operation results contained in the cache, see How to: create pre calculated tasks.

Handling exceptions in tasks

When a task throws one or more exceptions, the exception is wrapped in AggregateException Exception. The exception is propagated back to the thread connected to the task. Usually, the thread is waiting for the task to complete or the thread to access Result Properties. This behavior is mandatory NET Framework policy - by default, all unhandled exceptions should terminate the process. The calling code can handle exceptions by using any of the following methods in the {try/catch} block:

The task can also be accessed through the garbage collection thread before joining Exception Property to handle exceptions. By accessing this property, you can prevent unhandled exceptions from triggering the exception propagation behavior of terminating the process when the object is completed.

For more information about exceptions and tasks, see exception handling.

Cancel task

Task Class supports collaboration cancellation and collaboration with NET Framework 4 System.Threading.CancellationTokenSource Class sum System.Threading.CancellationToken Class is fully integrated.   System.Threading.Tasks.Task Most constructors in the class take CancellationToken Object as an input parameter. Many StartNew And Run Overloading also includes CancellationToken Parameters.

You can create tags and use CancellationTokenSource Class sends a cancellation request at a later time. You can pass this tag as a parameter to Task , you can also reference the same token in a user delegate that performs work in response to a cancellation request.

For more information, see Task cancellation and How to: cancel a task and its children.

TaskFactory class

TaskFactory Class provides static methods that encapsulate some common patterns for creating and starting tasks and continuing tasks.

Default TaskFactory Can be used as Task Class or Task<TResult> Class. You can also instantiate directly TaskFactory And specify various options, including CancellationToken,TaskCreationOptions Options TaskContinuationOptions Option or TaskScheduler . Any options specified when creating a task factory apply to all tasks it creates, unless Task By using TaskCreationOptions Enumeration (in this case, the options of the task override the options of the task factory).

Non delegated tasks

In some cases, you may need to use Task Encapsulates an asynchronous operation performed by an external component (not your own user delegate). If the operation is based on the asynchronous programming model Begin/End mode, you can use FromAsync Method. If this is not the case, you can use TaskCompletionSource<TResult> Object wraps the operation in the task and thus gets Task Some benefits of programmability, such as support for exception propagation and continuation. For more information, see TaskCompletionSource<TResult>.

Custom scheduler

Most application or library developers don't care about which processor the task runs on, how the task synchronizes its work with other tasks, and how it works on the System.Threading.ThreadPool Scheduled tasks in. They just need it to execute as efficiently as possible on the host. If you need more detailed control over the details of the plan, you can use the task parallel library to configure some settings on the default task scheduler, or even provide a custom scheduler. For more information, see TaskScheduler.

Related data structure

TPL has several new common types that are useful in both parallel and sequential schemes. They include System.Collections.Concurrent Some thread safe, fast and scalable collection classes in the namespace, as well as some new synchronization types (such as System.Threading.Semaphore And System.Threading.ManualResetEventSlim ), these new synchronization types are more efficient than the old synchronization types for a particular type of workload Other new types in NET Framework 4 (for example System.Threading.Barrier And System.Threading.SpinLock )Provides features not available in earlier versions. For more information, see Data structure for parallel programming.

Custom task type

It is recommended not to start from System.Threading.Tasks.Task Or System.Threading.Tasks.Task<TResult> Succession. Instead, we suggest you use AsyncState Property to associate other data or status with Task Or Task<TResult> Object. You can also extend using the extension method Task And Task<TResult> Class. For more information about extension methods, see Extension method and Extension method.

If you have to Task Or Task<TResult> Inheritance, you cannot use Run Or System.Threading.Tasks.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult> Or System.Threading.Tasks.TaskCompletionSource<TResult> Classes create instances of custom task types because these classes only create Task And Task<TResult> Object. In addition, it cannot be used Task,Task<TResult>,TaskFactory And TaskFactory<TResult> The provided task continuation mechanisms create instances of custom task types because these mechanisms only create instances Task And Task<TResult> Object.

Topics: C# .NET