基于任务的异步编程Task-based asynchronous programming

任务并行库 (TPL) 以“任务” 的概念为基础,后者表示异步操作。The Task Parallel Library (TPL) is based on the concept of a task, which represents an asynchronous operation. 在某些方面,任务类似于线程或 ThreadPool 工作项,但是抽象级别更高。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.

    在后台,任务排队到已使用算法增强的 ThreadPool,这些算法能够确定线程数并随之调整,提供负载平衡以实现吞吐量最大化。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.

    任务和围绕它们生成的框架提供了一组丰富的 API,这些 API 支持等待、取消、继续、可靠的异常处理、详细状态、自定义计划等功能。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.

出于这两个原因,在 .NET Framework 中,TPL 是用于编写多线程、异步和并行代码的首选 API。For both of these reasons, in the .NET Framework, TPL is the preferred API for writing multi-threaded, asynchronous, and parallel code.

隐式创建和运行任务Creating and running tasks implicitly

Parallel.Invoke 方法提供了一种简便方式,可同时运行任意数量的任意语句。The Parallel.Invoke method provides a convenient way to run any number of arbitrary statements concurrently. 只需为每个工作项传入 Action 委托即可。Just pass in an Action delegate for each item of work. 创建这些委托的最简单方式是使用 lambda 表达式。The easiest way to create these delegates is to use lambda expressions. lambda 表达式可调用指定的方法,或提供内联代码。The lambda expression can either call a named method or provide the code inline. 下面的示例演示一个基本的 Invoke 调用,该调用创建并启动同时运行的两个任务。The following example shows a basic Invoke call that creates and starts two tasks that run concurrently. 第一个任务由调用名为 DoSomeWork 的方法的 lambda 表达式表示,第二个任务由调用名为 DoSomeOtherWork 的方法的 lambda 表达式表示。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.

备注

本文档使用 lambda 表达式在 TPL 中定义委托。This documentation uses lambda expressions to define delegates in TPL. 如果不熟悉 C# 或 Visual Basic 中的 lambda 表达式,请参阅 PLINQ 和 TPL 中的 Lambda 表达式If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda Expressions in PLINQ and TPL.

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

备注

Task 在后台创建的 Invoke 实例数不一定与所提供的委托数相等。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. TPL 可能会使用各种优化,特别是对于大量的委托。The TPL may employ various optimizations, especially with large numbers of delegates.

有关详细信息,请参阅如何:使用 Parallel.Invoke 来执行并行操作For more information, see How to: Use Parallel.Invoke to Execute Parallel Operations.

为了更好地控制任务执行或从任务返回值,必须更加显式地使用 Task 对象。For greater control over task execution or to return a value from the task, you have to work with Task objects more explicitly.

显式创建和运行任务Creating and running tasks explicitly

不返回值的任务由 System.Threading.Tasks.Task 类表示。A task that does not return a value is represented by the System.Threading.Tasks.Task class. 返回值的任务由 System.Threading.Tasks.Task<TResult> 类表示,该类从 Task 继承。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. 例如,可以随时访问任务的 Status 属性,以确定它是已开始运行、已完成运行、已取消还是引发了异常。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. 状态由 TaskStatus 枚举表示。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. 该委托可以表示为命名的委托、匿名方法或 lambda 表达式。The delegate can be expressed as a named delegate, an anonymous method, or a lambda expression. lambda 表达式可以包含对命名方法的调用,如下面的示例所示。Lambda expressions can contain a call to a named method, as shown in the following example. 请注意,该示例包含对 Task.Wait 方法的调用,以确保任务在控制台模式应用程序结束之前完成执行。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.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
        Thread.CurrentThread.Name = "Main"
        
        ' Create a task and supply a user delegate by using a lambda expression. 
        Dim taskA = New Task(Sub() 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()
   End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

你还可以使用 Task.Run 方法通过一个操作创建并启动任务。You can also use the Task.Run methods to create and start a task in one operation. 无论是哪个任务计划程序与当前线程关联,Run 方法都将使用默认的任务计划程序来管理任务。To manage the task, the Run methods use the default task scheduler, regardless of which task scheduler is associated with the current thread. 不需要对任务的创建和计划进行更多控制时,首选 Run 方法创建并启动任务。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.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
        Thread.CurrentThread.Name = "Main"
        
        Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

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

你还可以使用 TaskFactory.StartNew 方法在一个操作中创建并启动任务。You can also use the TaskFactory.StartNew method to create and start a task in one operation. 不必将创建和计划分开并且需要其他任务创建选项或使用特定计划程序时,或者需要将其他状态传递到可以通过 Task.AsyncState 属性检索到的任务时,请使用此方法,如下例所示。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 that you can retrieve through its Task.AsyncState property, as shown in the following example.

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.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
   Public CreationTime As Long
   Public Name As Integer
   Public ThreadNum As Integer
End Class

Module Example
   Public Sub Main()
      Dim taskArray(9) As Task
      For i As Integer = 0 To taskArray.Length - 1
         taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                 Dim data As CustomData = TryCast(obj, CustomData)
                                                 If data Is Nothing Then Return
                                     
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                              End Sub,
                                              New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks} )
      Next
      Task.WaitAll(taskArray)     

      For Each task In taskArray
         Dim data = TryCast(task.AsyncState, CustomData)
         If data IsNot Nothing Then
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum)
         End If
      Next
   End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

TaskTask<TResult> 均公开静态 Factory 属性,该属性返回 TaskFactory 的默认实例,因此你可以调用该方法为 Task.Factory.StartNew()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(). 此外,在以下示例中,由于任务的类型为 System.Threading.Tasks.Task<TResult>,因此每个任务都具有包含计算结果的公共 Task<TResult>.Result 属性。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. 如果在计算完成之前访问 Result 属性,则该属性将阻止调用线程,直到值可用为止。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
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
        Dim taskArray() = { Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)), 
                            Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0)) }

        Dim results(taskArray.Length - 1) As Double
        Dim sum As Double
        
        For i As Integer = 0 To taskArray.Length - 1
            results(i) = taskArray(i).Result
            Console.Write("{0:N1} {1}", results(i), 
                              If(i = taskArray.Length - 1, "= ", "+ "))
            sum += results(i)
        Next
        Console.WriteLine("{0:N1}", sum)
   End Sub
   
   Private Function DoComputation(start As Double) As Double
      Dim sum As Double
      For value As Double = start To start + 10 Step .1
         sum += value
      Next
      Return sum 
   End Function
End Module
' 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.

使用 lambda 表达式创建委托时,你有权访问源代码中当时可见的所有变量。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. 然而,在某些情况下,特别是在循环中,lambda 不按照预期的方式捕获变量。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. 它将循环计数器传递给实例化 CustomData 对象并使用循环计数器作为对象标识符的 lambda 表达式。It passes a loop counter to a lambda expression that instantiates a CustomData object and uses the loop counter as the object's identifier. 如示例输出所示,每个 CustomData 对象都具有相同的标识符。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.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
   Public CreationTime As Long
   Public Name As Integer
   Public ThreadNum As Integer
End Class

Module Example
   Public Sub Main()
      ' Create the task object by using an Action(Of Object) to pass in the loop
      ' counter. This produces an unexpected result.
      Dim taskArray(9) As Task
      For i As Integer = 0 To taskArray.Length - 1
         taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                 Dim data As New CustomData With {.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)
                                              End Sub,
                                              i )
      Next
      Task.WaitAll(taskArray)     
   End Sub
End Module
' 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. 以下示例在上一示例的基础上做了修改,在创建 CustomData 对象时使用循环计数器,该对象继而传递给 lambda 表达式。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. 如示例输出所示,每个 CustomData 对象现在都具有唯一的一个标识符,该标识符基于该对象实例化时循环计数器的值。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.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
   Public CreationTime As Long
   Public Name As Integer
   Public ThreadNum As Integer
End Class

Module Example
   Public Sub 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. 
      Dim taskArray(9) As Task
      For i As Integer = 0 To taskArray.Length - 1
         taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                 Dim data As CustomData = TryCast(obj, CustomData)
                                                 If data Is Nothing Then Return
                                     
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum)
                                              End Sub,
                                              New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks} )
      Next
      Task.WaitAll(taskArray)     
   End Sub
End Module
' 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.AsyncState 属性从任务对象访问。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. 它使用 AsyncState 属性显示关于传递到 lambda 表达式的 CustomData 对象的信息。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.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
   Public CreationTime As Long
   Public Name As Integer
   Public ThreadNum As Integer
End Class

Module Example
   Public Sub Main()
      Dim taskArray(9) As Task
      For i As Integer = 0 To taskArray.Length - 1
         taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                 Dim data As CustomData = TryCast(obj, CustomData)
                                                 If data Is Nothing Then Return
                                     
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                              End Sub,
                                              New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks} )
      Next
      Task.WaitAll(taskArray)     

      For Each task In taskArray
         Dim data = TryCast(task.AsyncState, CustomData)
         If data IsNot Nothing Then
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum)
         End If
      Next
   End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

任务 IDTask ID

每个任务都获得一个在应用程序域中唯一标识自己的整数 ID,可以使用 Task.Id 属性访问该 ID。Every task receives an integer ID that uniquely identifies it in an application domain and can be accessed by using the Task.Id property. 该 ID 可有效用于在 Visual Studio 调试器的“并行堆栈” 和“任务” 窗口中查看任务信息。The ID is useful for viewing task information in the Visual Studio debugger Parallel Stacks and Tasks windows. 该 ID 是惰式创建的,这意味着它不会在被请求之前创建;因此每次运行该程序时,任务可能具有不同的 ID。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. 有关如何在调试器中查看任务 ID 的详细信息,请参阅使用任务窗口使用并行堆栈窗口For more information about how to view task IDs in the debugger, see Using the Tasks Window and Using the Parallel Stacks Window.

任务创建选项Task creation options

创建任务的大多数 API 提供接受 TaskCreationOptions 参数的重载。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 参数值TaskCreationOptions parameter value 说明Description
None 未指定任何选项时的默认值。The default when no option is specified. 计划程序将使用其默认试探法来计划任务。The scheduler uses its default heuristics to schedule the task.
PreferFairness 指定应当计划任务,以使越早创建的任务将更可能越早执行,而越晚创建的任务将更可能越晚执行。Specifies 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.
LongRunning 指定该任务表示长时间运行的运算。Specifies that the task represents a long-running operation.
AttachedToParent 指定应将任务创建为当前任务(如果存在)的附加子级。Specifies 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.
DenyChildAttach 指定如果内部任务指定 AttachedToParent 选项,则该任务不会成为附加的子任务。Specifies that if an inner task specifies the AttachedToParent option, that task will not become an attached child task.
HideScheduler 指定通过调用特定任务内部的 TaskFactory.StartNewTask<TResult>.ContinueWith 等方法创建的任务的任务计划程序是默认计划程序,而不是正在运行此任务的计划程序。Specifies 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.

可以通过使用位 OR 运算组合选项。The options may be combined by using a bitwise OR operation. 下面的示例演示一个具有 LongRunningPreferFairness 选项的任务。The following example shows a task that has the LongRunning and PreferFairness option.

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

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

任务、线程和区域性Tasks, threads, and culture

每个线程都具有一个关联的区域性和 UI 区域性,分别由 Thread.CurrentCultureThread.CurrentUICulture 属性定义。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. 线程的 UI 区域性用于查找资源。A thread's UI culture is used in resource lookup. 通常,除非使用 CultureInfo.DefaultThreadCurrentCultureCultureInfo.DefaultThreadCurrentUICulture 属性在应用程序域中为所有线程指定默认区域性,线程的默认区域性和 UI 区域性则由系统区域性定义。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. 基于任务的应用编程模型遵循这种做法,这些应用指定 .NET Framework 4.6 之前的 .NET Framework 版本。The task-based programming model for apps that target versions of the .NET Framework prior to .NET Framework 4.6 adhere to this practice.

重要

请注意,作为任务上下文一部分的调用线程的区域性适用于面向.NET Framework 4.6 的应用,而不是在 .NET Framework 4.6 下运行的应用 。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. 可以在 Visual Studio 中创建项目时,定目标到特定版本的 .NET Framework,具体操作是从“新建项目” 对话框顶部的下拉列表选择相应版本。在 Visual Studio 外部,可以使用 TargetFrameworkAttribute 属性定目标到特定版本。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. 对于指定 .NET Framework 4.6 之前的 .NET Framework 版本的应用,或者对于不指定 .NET Framework 特定版本的应用,任务的区域性将继续由它运行的线程的区域性确定。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.

启动指定 .NET Framework 4.6 的应用程序,即使任务在线程池线程上以异步方式运行,调用线程的区域性仍然通过每个任务继承。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. 它使用 TargetFrameworkAttribute 特性来指定 .NET Framework 4.6,并将应用程序的当前区域性更改为 French (France),或者,如果 French (France) 已为当前区域性,则更改为 English (United States)。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). 然后,调用一个名为 formatDelegate 的委托,该委托返回在新区域性中格式化为货币值的数字。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 €
Imports System.Globalization
Imports System.Runtime.Versioning
Imports System.Threading
Imports System.Threading.Tasks

<Assembly:TargetFramework(".NETFramework,Version=v4.6")>

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 €
' 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 €

如果使用的是 Visual Studio,可以省略 TargetFrameworkAttribute 属性,并在创建项目时在“新建项目” 对话框中改选“.NET Framework 4.6”作为目标。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.

对于反映指定 .NET Framework 4.6 之前的 .NET Framework 版本的应用程序行为的输出,请从源代码中移除 TargetFrameworkAttribute 属性。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.

有关异步任务和区域性的详细信息,请参阅 CultureInfo 主题中的“区域性和基于异步任务的操作”部分。For more information on asynchronous tasks and culture, see the "Culture and asynchronous task-based operations" section in the CultureInfo topic.

创建任务延续Creating task continuations

使用 Task.ContinueWithTask<TResult>.ContinueWith 方法,可以指定要在先行任务 完成时启动的任务。The Task.ContinueWith and Task<TResult>.ContinueWith methods let you specify a task to start when the antecedent task finishes. 延续任务的委托已传递了对先行任务的引用,因此它可以检查先行任务的状态,并通过检索 Task<TResult>.Result 属性的值将先行任务的输出用作延续任务的输入。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.

在下面的示例中,getData 任务通过调用 TaskFactory.StartNew<TResult>(Func<TResult>) 方法来启动。In the following example, the getData task is started by a call to the TaskFactory.StartNew<TResult>(Func<TResult>) method. processData 完成时,getData 任务自动启动,当 displayData 完成时,processData 启动。The processData task is started automatically when getData finishes, and displayData is started when processData finishes. getData 产生一个整数数组,通过 processData 任务的 getData 属性,Task<TResult>.Result 任务可访问该数组。getData produces an integer array, which is accessible to the processData task through the getData task's Task<TResult>.Result property. processData 任务处理该数组并返回结果,结果的类型从传递到 Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) 方法的 Lambda 表达式的返回类型推断而来。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. displayData 完成时,processData 任务自动执行,而 Tuple<T1,T2,T3> 任务可通过 processData 任务的 displayData 属性访问由 processData lambda 表达式返回的 Task<TResult>.Result 对象。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. displayData 任务采用 processData 任务的结果,继而得出自己的结果,其类型以相似方式推断而来,且可由程序中的 Result 属性使用。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
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim getData = Task.Factory.StartNew(Function() 
                                             Dim rnd As New Random()
                                             Dim values(99) As Integer
                                             For ctr = 0 To values.GetUpperBound(0)
                                                values(ctr) = rnd.Next()
                                             Next
                                             Return values
                                          End Function)  
      Dim processData = getData.ContinueWith(Function(x)
                                                Dim n As Integer = x.Result.Length
                                                Dim sum As Long
                                                Dim mean As Double
                                  
                                                For ctr = 0 To x.Result.GetUpperBound(0)
                                                   sum += x.Result(ctr)
                                                Next
                                                mean = sum / n
                                                Return Tuple.Create(n, sum, mean)
                                             End Function) 
      Dim displayData = processData.ContinueWith(Function(x)
                                                    Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2, 
                                                                         x.Result.Item3)
                                                 End Function)                         
      Console.WriteLine(displayData.Result)
   End Sub                        
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

因为 Task.ContinueWith 是实例方法,所以你可以将方法调用链接在一起,而不是为每个先行任务去实例化 Task<TResult> 对象。Because Task.ContinueWith is an instance method, you can chain method calls together instead of instantiating a Task<TResult> object for each antecedent task. 以下示例与上一示例在功能上等同,唯一的不同在于它将对 Task.ContinueWith 方法的调用链接在一起。The following example is functionally identical to the previous example, except that it chains together calls to the Task.ContinueWith method. 请注意,通过方法调用链返回的 Task<TResult> 对象是最终延续任务。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
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim displayData = Task.Factory.StartNew(Function() 
                                                 Dim rnd As New Random()
                                                 Dim values(99) As Integer
                                                 For ctr = 0 To values.GetUpperBound(0)
                                                    values(ctr) = rnd.Next()
                                                 Next
                                                 Return values
                                              End Function). _  
                        ContinueWith(Function(x)
                                        Dim n As Integer = x.Result.Length
                                        Dim sum As Long
                                        Dim mean As Double
                                  
                                        For ctr = 0 To x.Result.GetUpperBound(0)
                                           sum += x.Result(ctr)
                                        Next
                                        mean = sum / n
                                        Return Tuple.Create(n, sum, mean)
                                     End Function). _ 
                        ContinueWith(Function(x)
                                        Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,  
                                                             x.Result.Item3)
                                     End Function)                         
      Console.WriteLine(displayData.Result)
   End Sub                        
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

使用 ContinueWhenAllContinueWhenAny 方法,可以从多个任务继续。The ContinueWhenAll and ContinueWhenAny methods enable you to continue from multiple tasks.

有关详细信息,请参阅使用延续任务链接任务For more information, see Chaining Tasks by Using Continuation Tasks.

创建分离的子任务Creating detached child tasks

如果在任务中运行的用户代码创建一个新任务,且未指定 AttachedToParent 选项,则该新任务不采用任何特殊方式与父任务同步。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.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

请注意,父任务不会等待分离子任务完成。Note that the parent task does not wait for the detached child task to finish.

创建子任务Creating child tasks

如果任务中运行的用户代码在创建任务时指定了 AttachedToParent 选项,新任务就称为父任务的附加子任务 。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. 因为父任务隐式地等待所有附加子任务完成,所以你可以使用 AttachedToParent 选项表示结构化的任务并行。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. 请注意,虽然此示例调用 Task.Wait 方法等待父任务完成,但不必显式等待附加子任务完成。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.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Factory.StartNew(Sub()
                                            Console.WriteLine("Parent task beginning.")
                                            For ctr As Integer = 0 To 9
                                               Dim taskNo As Integer = ctr
                                               Task.Factory.StartNew(Sub(x)
                                                                        Thread.SpinWait(5000000)
                                                                        Console.WriteLine("Attached child #{0} completed.", 
                                                                                          x)
                                                                     End Sub,
                                                                     taskNo, TaskCreationOptions.AttachedToParent)
                                            Next
                                         End Sub)
      parent.Wait()
      Console.WriteLine("Parent task completed.")
   End Sub
End Module   
' 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.

父任务可使用 TaskCreationOptions.DenyChildAttach 选项阻止其他任务附加到父任务。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.

等待任务完成Waiting for tasks to finish

System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> 类型提供了 Task.Wait 方法的若干重载,以便能够等待任务完成。The System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types provide several overloads of the Task.Wait methods that enable you to wait for a task to finish. 此外,使用静态 Task.WaitAllTask.WaitAny 方法的重载可以等待一批任务中的任一任务或所有任务完成。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. 例如,执行 Main(应用程序入口点)中的所有同步代码后,控制台应用程序将立即终止。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...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

有关演示异常处理的示例,请参见异常处理For an example that shows exception handling, see Exception Handling.

某些重载允许你指定超时,而其他重载采用额外的 CancellationToken 作为输入参数,以便可以通过编程方式或根据用户输入来取消等待。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.

等待任务时,其实是在隐式等待使用 TaskCreationOptions.AttachedToParent 选项创建的该任务的所有子级。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 在该任务已完成时立即返回。Task.Wait returns immediately if the task has already completed. Task.Wait 方法将抛出由某任务引发的任何异常,即使 Task.Wait 方法是在该任务完成之后调用的。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.

组合任务Composing tasks

Task 类和 Task<TResult> 类提供多种方法,这些方法能够帮助你组合多个任务以实现常见模式,并更好地使用由 C#、Visual Basic 和 F# 提供的异步语言功能。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#. 本节介绍了 WhenAllWhenAnyDelayFromResult 方法。This section describes the WhenAll, WhenAny, Delay, and FromResult methods.

Task.WhenAllTask.WhenAll

Task.WhenAll 方法异步等待多个 TaskTask<TResult> 对象完成。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. 例如,你可以等待多个 TaskTask<TResult> 对象在一个方法调用中完成。For example, you can wait for multiple Task and Task<TResult> objects to complete from one method call.

Task.WhenAnyTask.WhenAny

Task.WhenAny 方法异步等待多个 TaskTask<TResult> 对象中的一个完成。The Task.WhenAny method asynchronously waits for one of multiple Task or Task<TResult> objects to finish. 与在 Task.WhenAll 方法中一样,该方法提供重载版本,让你能等待非均匀任务组。As in the Task.WhenAll method, this method provides overloaded versions that enable you to wait for non-uniform sets of tasks. WhenAny 方法在下列情境中尤其有用。The WhenAny method is especially useful in the following scenarios.

  • 冗余运算。Redundant operations. 请考虑可以用多种方式执行的算法或运算。Consider an algorithm or operation that can be performed in many ways. 你可使用 WhenAny 方法来选择先完成的运算,然后取消剩余的运算。You can use the WhenAny method to select the operation that finishes first and then cancel the remaining operations.

  • 交叉运算。Interleaved operations. 你可启动必须全部完成的多项运算,并使用 WhenAny 方法在每项运算完成时处理结果。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. 你可使用 WhenAny 方法通过限制并发运算的数量来扩展前面的情境。You can use the WhenAny method to extend the previous scenario by limiting the number of concurrent operations.

  • 过期的运算。Expired operations. 你可使用 WhenAny 方法在一个或多个任务与特定时间后完成的任务(例如 Delay 方法返回的任务)间进行选择。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. 下节描述了 Delay 方法。The Delay method is described in the following section.

Task.DelayTask.Delay

Task.Delay 方法将生成在指定时间后完成的 Task 对象。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).FromResultTask(T).FromResult

通过使用 Task.FromResult 方法,你可以创建包含预计算结果的 Task<TResult> 对象。By using the Task.FromResult method, you can create a Task<TResult> object that holds a pre-computed result. 执行返回 Task<TResult> 对象的异步运算,且已计算该 Task<TResult> 对象的结果时,此方法将十分有用。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. 有关使用 FromResult 检索缓存中包含的异步下载运算结果的示例,请参阅如何:创建预先计算的任务For an example that uses FromResult to retrieve the results of asynchronous download operations that are held in a cache, see How to: Create Pre-Computed Tasks.

处理任务中的异常Handling exceptions in tasks

当某个任务抛出一个或多个异常时,异常包装在 AggregateException 异常中。When a task throws one or more exceptions, the exceptions are wrapped in an AggregateException exception. 该异常传播回与该任务联接的线程,通常该线程正在等待该任务完成或该线程访问 Result 属性。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. 此行为用于强制实施 .NET Framework 策略 - 默认所有未处理的异常应终止进程。This behavior serves to enforce the .NET Framework policy that all unhandled exceptions by default should terminate the process. 调用代码可以通过使用 try/catch 块中的以下任意方法来处理异常:The calling code can handle the exceptions by using any of the following in a try/catch block:

联接线程也可以通过在对任务进行垃圾回收之前访问 Exception 属性来处理异常。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.

取消任务Canceling tasks

Task 类支持协作取消,并与 .NET Framework 4 中新增的 System.Threading.CancellationTokenSource 类和 System.Threading.CancellationToken 类完全集成。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. System.Threading.Tasks.Task 类中的大多数构造函数采用 CancellationToken 对象作为输入参数。Many of the constructors in the System.Threading.Tasks.Task class take a CancellationToken object as an input parameter. 许多 StartNewRun 重载还包括 CancellationToken 参数。Many of the StartNew and Run overloads also include a CancellationToken parameter.

你可以创建标记,并使用 CancellationTokenSource 类在以后某一时间发出取消请求。You can create the token, and issue the cancellation request at some later time, by using the CancellationTokenSource class. 可以将该标记作为参数传递给 Task,还可以在执行响应取消请求的工作的用户委托中引用同一标记。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.

TaskFactory 类The TaskFactory class

TaskFactory 类提供静态方法,这些方法封装了用于创建和启动任务和延续任务的一些常用模式。The TaskFactory class provides static methods that encapsulate some common patterns for creating and starting tasks and continuation tasks.

默认的 TaskFactory 可作为 Task 类或 Task<TResult> 类上的静态属性访问。The default TaskFactory can be accessed as a static property on the Task class or Task<TResult> class. 你还可以直接实例化 TaskFactory 并指定各种选项,包括 CancellationTokenTaskCreationOptions 选项、TaskContinuationOptions 选项或 TaskSchedulerYou can also instantiate a TaskFactory directly and specify various options that include a CancellationToken, a TaskCreationOptions option, a TaskContinuationOptions option, or a TaskScheduler. 创建任务工厂时所指定的任何选项将应用于它创建的所有任务,除非 Task 是通过使用 TaskCreationOptions 枚举创建的(在这种情况下,任务的选项重写任务工厂的选项)。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.

无委托的任务Tasks without delegates

在某些情况下,可能需要使用 Task 封装由外部组件(而不是你自己的用户委托)执行的某个异步操作。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. 如果该操作基于异步编程模型 Begin/End 模式,你可以使用 FromAsync 方法。If the operation is based on the Asynchronous Programming Model Begin/End pattern, you can use the FromAsync methods. 如果不是这种情况,你可以使用 TaskCompletionSource<TResult> 对象将该操作包装在任务中,并因而获得 Task 可编程性的一些好处,例如对异常传播和延续的支持。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. 有关详细信息,请参阅 TaskCompletionSource<TResult>For more information, see TaskCompletionSource<TResult>.

自定义计划程序Custom schedulers

大多数应用程序或库开发人员并不关心任务在哪个处理器上运行、任务如何将其工作与其他任务同步以及如何在 System.Threading.ThreadPool 中计划任务。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. 有关详细信息,请参阅 TaskSchedulerFor more information, see TaskScheduler.

TPL 有几种在并行和顺序方案中都有用的新公共类型。The TPL has several new public types that are useful in both parallel and sequential scenarios. 它们包括 System.Collections.Concurrent 命名空间中的一些线程安全的、快速且可缩放的集合类,还包括一些新的同步类型(例如 System.Threading.SemaphoreSystem.Threading.ManualResetEventSlim),对特定类型的工作负荷而言,这些新同步类型比旧的同步类型效率更高。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. .NET Framework 4 中的其他新类型(例如 System.Threading.BarrierSystem.Threading.SpinLock)提供了早期版本中未提供的功能。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.

自定义任务类型Custom task types

建议不要从 System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> 继承。We recommend that you do not inherit from System.Threading.Tasks.Task or System.Threading.Tasks.Task<TResult>. 相反,我们建议你使用 AsyncState 属性将其他数据或状态与 TaskTask<TResult> 对象相关联。Instead, we recommend that you use the AsyncState property to associate additional data or state with a Task or Task<TResult> object. 还可以使用扩展方法扩展 TaskTask<TResult> 类的功能。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.

如果必须从 TaskTask<TResult> 继承,则不能使用 RunSystem.Threading.Tasks.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult>System.Threading.Tasks.TaskCompletionSource<TResult> 类创建自定义任务类型的实例,因为这些类仅创建 TaskTask<TResult> 对象。If you must inherit from Task or Task<TResult>, you cannot use Run, or the System.Threading.Tasks.TaskFactory, System.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. 此外,不能使用 TaskTask<TResult>TaskFactoryTaskFactory<TResult> 提供的任务延续机制创建自定义任务类型的实例,因为这些机制也只创建 TaskTask<TResult> 对象。In addition, you cannot use the task continuation mechanisms that are provided by Task, Task<TResult>, TaskFactory, and TaskFactory<TResult> to create instances of your custom task type because these mechanisms also create only Task and Task<TResult> objects.

TitleTitle 说明Description
使用延续任务来链接任务Chaining Tasks by Using Continuation Tasks 描述延续任务的工作方式。Describes how continuations work.
附加和分离的子任务Attached and Detached Child Tasks 描述附加子任务和分离子任务之间的差异。Describes the difference between attached and detached child tasks.
任务取消Task Cancellation 描述在 Task 对象中内置的取消支持。Describes the cancellation support that is built into the Task object.
异常处理Exception Handling 描述如何处理并行线程中的异常。Describes how exceptions on concurrent threads are handled.
如何:使用 Parallel.Invoke 来执行并行操作How to: Use Parallel.Invoke to Execute Parallel Operations 描述如何使用 InvokeDescribes how to use Invoke.
如何:从任务中返回值How to: Return a Value from a Task 描述如何从任务中返回值。Describes how to return values from tasks.
如何:取消任务及其子级How to: Cancel a Task and Its Children 描述如何取消任务。Describes how to cancel tasks.
如何:创建预先计算的任务How to: Create Pre-Computed Tasks 描述如何使用 Task.FromResult 方法去检索缓存中包含的异步下载运算结果。Describes how to use the Task.FromResult method to retrieve the results of asynchronous download operations that are held in a cache.
如何:使用并行任务遍历二叉树How to: Traverse a Binary Tree with Parallel Tasks 描述如何使用任务遍历二叉树。Describes how to use tasks to traverse a binary tree.
如何:解除嵌套任务的包装How to: Unwrap a Nested Task 演示如何使用 Unwrap 扩展方法。Demonstrates how to use the Unwrap extension method.
数据并行Data Parallelism 描述如何使用 ForForEach 来创建循环访问数据的并行循环。Describes how to use For and ForEach to create parallel loops over data.
并行编程Parallel Programming .NET Framework 并行编程的顶级节点。Top level node for .NET Framework parallel programming.

请参阅See also