工作型非同步程式設計Task-based asynchronous programming

工作平行程式庫 (TPL) 是以「工作」(Task) 的概念為基礎,工作表示非同步作業。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. 「工作平行處理原則」(Task Parallelism) 是指同時執行一個或多個獨立工作。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,可支援等候、取消、接續、穩固的例外狀況處理、詳細狀態和自訂排程等各式各樣的作業。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 是撰寫多執行緒、非同步和平行程式碼時較好的應用程式開發介面。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. 它會將迴圈計數器傳遞給 Lambda 運算式,這個運算式會具現化 CustomData 物件並使用迴圈計數器做為物件的識別項。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,這個 ID 負責在應用程式定義域中唯一識別該工作,且可以經由 Task.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. 當要在 Visual Studio Debugger 的 [平行堆疊] 和 [工作] 視窗中檢閱工作資訊時,這個 ID 很有用。The ID is useful for viewing task information in the Visual Studio debugger Parallel Stacks and Tasks windows. ID 是採延遲建立的方式,也就是說,要等到收到要求後才會建立 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. 如需如何在偵錯工具中檢閱工作識別碼的詳細資訊,請參閱使用工作視窗使用平行堆疊視窗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 設為目標,並將應用程式目前的文化特性變更為「法文 (法國)」(如果「法文 (法國)」已經是目前的文化特性,則變更為「英文 (美國)」)。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 方法可讓您指定在「前項工作」(Antecedent Task) 完成時要啟動的工作。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 完成時會啟動 processDataThe processData task is started automatically when getData finishes, and displayData is started when processData finishes. getData 會產生可透過 getData 工作的 Task<TResult>.Result 屬性來存取 processData 工作的整數陣列。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> Lambda 運算式傳回的 processData 物件可供 displayData 工作透過 processData 工作的 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. 這種非同步處理的工作稱為「中斷連結的巢狀工作」(Detached Nested Task) 或「中斷連結的子工作」(Detached Child Task)。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 選項建立新工作時,新的工作即稱為父工作的「附加的子工作」(Attached Child Task)。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

TaskTask<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.CancellationTokenSourceSystem.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. 您在建立工作 Factory 時指定的任何選項都會套用至此 Factory 建立的所有工作,除非 Task 是以 TaskCreationOptions 列舉建立,在此情況下,工作的選項會覆寫工作 Factory 的選項。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.

標題Title 描述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