工作型非同步程式設計

工作平行程式庫 (TPL) 是以「工作」(Task) 的概念為基礎,工作表示非同步作業。 在某些方面,工作類似執行緒或 ThreadPool 工作項目,但是抽象等級較高。 「工作平行處理原則」(Task Parallelism) 是指同時執行一個或多個獨立工作。 工作主要提供兩項優點:

  • 更有效率且更靈活地使用系統資源。

    在幕後,工作會排入已用演算法加強的 ThreadPool 佇列中,這些演算法會判斷和調整執行緒數目。 這些演算法提供負載平衡,以發揮最大輸送量。 該程序會使工作變得相當輕便,而且您可以建立許多工作來啟用細部的平行處理原則。

  • 比使用執行緒或工作項目提供更多的程式設計控制能力。

    工作和以工作為中心建置的架構提供了一組豐富的 API,可支援等候、取消、接續、穩固的例外狀況處理、詳細狀態和自訂排程等各式各樣的作業。

基於這兩個理由,TPL 是在 .NET 中撰寫多執行緒、非同步和平行程式碼時較好的 API。

隱含建立和執行工作

Parallel.Invoke 方法有便利的方式可讓您同時執行任何數目的任意陳述式。 只要為每個工作項目傳入 Action 委派即可。 若要建立這些委派,使用 Lambda 運算式是最簡單的方式。 Lambda 運算式可以呼叫具名方法,或提供程式碼內嵌。 下列範例說明如何使用基本 Invoke 呼叫,建立並啟動兩項同時執行的工作。 第一個工作是由呼叫名為 DoSomeWork 之方法的 Lambda 運算式表示,而第二個工作是由呼叫名為 DoSomeOtherWork 之方法的 Lambda 運算式表示。

注意

本文件使用 Lambda 運算式來定義 TPL 中的委派。 如果您不熟悉 C# 或 Visual Basic 中的 Lambda 運算式,請參閱 PLINQ 和 TPL 中的 Lambda 運算式

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

注意

Invoke 在幕後建立的 Task 執行個體數目,不一定會等於所提供的委派數目。 TPL 可採用各種不同的最佳化方式,尤其是有大量委派時。

如需詳細資訊,請參閱如何:使用 Parallel.Invoke 來執行平行作業

若要進一步控制工作執行,或是從工作傳回值,您必須更明確地使用 Task 物件。

明確建立和執行工作

不會傳回值的工作是以 System.Threading.Tasks.Task 類別表示。 會傳回值的工作則是以繼承自 System.Threading.Tasks.Task<TResult>Task 類別表示。 工作物件會處理基礎結構細節,並提供可供呼叫端執行緒在整個工作存留期存取的方法和屬性。 例如,您可以隨時存取工作的 Status 屬性,看看工作是否已開始執行、已執行完畢、已取消或已擲回例外狀況。 狀態是以 TaskStatus 列舉表示。

當您建立工作時,您會指定使用者委派給工作,這個使用者委派會封裝此工作會執行的程式碼。 委派可以以具名委派、匿名方法或 Lambda 運算式的形式呈現。 Lambda 運算式可以包含具名方法的呼叫,如下列範例所示。 此範例會包含對 Task.Wait 方法的呼叫,以確保工作會在主控台模式應用程式結束前完成執行。

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

public class Lambda
{
   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 as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    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.
End Namespace

您也可以使用 Task.Run 方法直接以一道作業建立並啟動工作。 不論與目前執行緒相關聯的是哪個工作排程器,Run 方法都會使用預設的工作排程器來管理工作。 若不需要對工作的建立和排程多加控制,則 Run 方法是建立和啟動工作的較好方式。

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

namespace Run;

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 as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    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.
End Namespace

您也可以使用 TaskFactory.StartNew 方法直接以一道作業建立並啟動工作。 如下列範例所示,您可以在下列情況使用此方法:

  • 不需要分開建立和排程,而且您需要額外的工作建立選項或使用特定排程器。

  • 您必須將其他狀態傳遞至可透過其 Task.AsyncState 屬性擷取的工作。

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

namespace TaskIntro;

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

public class AsyncState
{
    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, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    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 = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

TaskTask<TResult> 都會公開靜態 Factory 屬性,這個屬性會傳回 TaskFactory 的預設執行個體,讓您以 Task.Factory.StartNew() 的形式呼叫這個方法。 另外,在下面範例中,每個工作都屬於 System.Threading.Tasks.Task<TResult> 類型,因此它們都各自具有包含計算結果的公用 Task<TResult>.Result 屬性。 這些工作會以非同步方式執行,且可能以任何順序完成。 如果在計算完成之前就存取 Result 屬性,則這個屬性會封鎖呼叫端執行緒,直到有值為止。

using System;
using System.Threading.Tasks;

public class Result
{
   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

Namespace Result
    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
End Namespace

如需詳細資訊,請參閱如何:從工作傳回值

當您使用 Lambda 運算式建立委派時,可以存取原始程式碼中該時間點可見的所有變數。 不過,在某些情況下 (特別是在迴圈內),Lambda 擷取的變數不是預期的變數。 該工作只會擷取變數 (而不是值) 的參考,因為會在每個反復項目之後變動。 下面範例會說明此問題。 它會將迴圈計數器傳遞給 Lambda 運算式,這個運算式會具現化 CustomData 物件並使用迴圈計數器做為物件的識別項。 如範例輸出所示,每個 CustomData 物件都具有相同的識別項。

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

namespace Example.Iterations;

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

public class IterationTwo
{
   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

Namespace IterationsTwo
    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 = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         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.
End Namespace

您可以透過工作的建構函式提供狀態物件給工作,以存取每個反覆項目的值。 下面範例是修改自先前的範例,是在建立 CustomData 物件 (此物件接著傳遞至 Lambda 運算式) 時使用迴圈計數器。 如範例輸出所示,每個 CustomData 物件現在會在物件具現化時根據迴圈計數器的值而擁有唯一識別項。

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

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

public class IterationOne
{
   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

Namespace IterationsOne
    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 = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.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.
End Namespace

這個狀態會做為引數傳遞給工作委派,且可使用 Task.AsyncState 屬性從工作物件存取。 下面範例是改變自先前的範例。 此範例會使用 AsyncState 屬性來顯示有關傳遞至 Lambda 運算式之 CustomData 物件的資訊。

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

namespace TaskIntro;

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

public class AsyncState
{
    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, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    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 = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

工作識別碼

每個工作都會得到一個整數 ID,這個 ID 負責在應用程式定義域中唯一識別該工作,且可以經由 Task.Id 屬性來存取。 當要在 Visual Studio Debugger 的 [平行堆疊] 和 [工作] 視窗中檢閱工作資訊時,這個 ID 很有用。 識別碼會延遲建立,這表示在收到要求之前不會建立識別碼。 因此,每次執行程式時,工作都可能會有不同的識別碼。 如需如何在偵錯工具中檢閱工作識別碼的詳細資訊,請參閱使用工作視窗使用平行堆疊視窗

工作建立選項

大多數建立工作的 API 都會提供用來接受 TaskCreationOptions 參數的多載。 您可以指定其中一或多個選項,指示工作排程器要如何在執行緒集區上排定工作。 選項可以透過位元 OR 運算結合在一起。

下列範例顯示具有 LongRunningPreferFairness 選項的工作:

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

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

工作、執行緒和文化特性

每個執行緒都有相關聯的文化特性和 UI 文化特性,分別由 Thread.CurrentCultureThread.CurrentUICulture 屬性定義。 執行緒的文化特性會用在格式化、剖析、排序及字串比較作業之類的作業。 執行緒的 UI 文化特性會用於資源查閱。

系統文化特性會定義執行緒的預設文化特性和 UI 文化特性。 不過,您可以使用 CultureInfo.DefaultThreadCurrentCultureCultureInfo.DefaultThreadCurrentUICulture 屬性,為應用程式定義域中的所有執行緒指定預設文化特性。 如果您明確設定執行緒的文化特性,並啟動新的執行緒,新的執行緒並不會繼承呼叫執行緒的文化特性;相反地,其文化特性為預設系統文化特性。 不過,在以工作為基礎的程式設計中,工作會使用呼叫執行緒的文化特性,即使工作在不同的執行緒上以非同步方式執行也一樣。

下列範例提供一個簡單的範例。 工作會將應用程式的目前文化特性變更為法文 (法國)。 如果法文 (法國) 已經是目前文化特性,則會變更為英文 (美國)。 然後它叫用名為 formatDelegate 的委派,並傳回在新的文化特性中格式化為貨幣值的一些數字。 不論委派是由工作以同步或非同步方式叫用,工作都會使用呼叫執行緒的文化特性。

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

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

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

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

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

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

注意

在 .NET Framework 4.6 之前的 .NET Framework 版本中,工作的文化特性取決於其執行所在的執行緒文化特性,而不是呼叫執行緒的文化特性。 針對非同步工作,工作所使用的文化特性可能會與呼叫執行緒的文化特性不同。

如需有關非同步工作和文化特性的詳細資訊,請參閱 CultureInfo 文章中的<文化特性和以工作為基礎的非同步作業>一節。

建立工作接續

Task.ContinueWithTask<TResult>.ContinueWith 方法可讓您指定在「前項工作」(Antecedent Task) 完成時要啟動的工作。 接續工作的委派會傳遞前項工作的參考,以便檢查前項工作的狀態。 藉由擷取 Task<TResult>.Result 屬性的值,您可以使用前項輸出做為接續的輸入。

在下面範例中,getData 工作是透過呼叫 TaskFactory.StartNew<TResult>(Func<TResult>) 方法來啟動。 當 processData 完成時,會自動啟動 getData 工作,而 displayData 完成時會啟動 processDatagetData 會產生整數陣列,processData 工作可透過 getData 工作的 Task<TResult>.Result 屬性存取該陣列。 processData 工作會處理該陣列並傳回結果,這個結果的類型是從傳遞給 Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) 方法之 Lambda 運算式的傳回類型推斷而來。 當 displayData 完成時,processData 工作會自動執行,而 Tuple<T1,T2,T3> Lambda 運算式傳回的 processData 物件可供 displayData 工作透過 processData 工作的 Task<TResult>.Result 屬性來存取。 displayData 工作會接受 processData 工作的結果並且產生結果。 該結果的類型也是以類似方式推斷而來,且這個結果會放在 Result 屬性中讓程式取得。

using System;
using System.Threading.Tasks;

public class ContinuationOne
{
   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

Namespace ContinuationsOne
    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
End Namespace

由於 Task.ContinueWith 是執行個體方法,所以您可以將方法呼叫鏈結在一起 (而不是具現化每個前項工作的 Task<TResult> 物件)。 下面範例在功能上與前一個範例相同,不同之處在於會將對 Task.ContinueWith 方法的呼叫鏈結在一起。 由方法呼叫鏈結所傳回的 Task<TResult> 物件是最後接續工作。

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   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

Namespace ContinuationsTwo
    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
End Namespace

ContinueWhenAllContinueWhenAny 方法可讓您從多個工作繼續執行。

如需詳細資訊,請參閱使用接續工作鏈結工作

建立中斷連結的子工作

當在工作內執行的使用者程式碼建立新工作,卻未指定 AttachedToParent 選項時,新的工作不會以任何特殊方式與父工作保持同步。 這種非同步處理的工作稱為「中斷連結的巢狀工作」(Detached Nested Task) 或「中斷連結的子工作」(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.

注意

父工作不會等候中斷連結的子工作完成。

建立子工作

當在工作內執行的使用者程式碼以 AttachedToParent 選項建立新工作時,新工作即稱為父工作附加的子工作。 您可以使用 AttachedToParent 選項來呈現結構化工作平行處理原則,因為父工作會隱含等候所有附加的子工作都完成。 下面範例會示範一個父工作如何建立十個附加的子工作。 此範例會呼叫 Task.Wait 方法,以等候父工作完成。 這不需要明確等候附加的子工作完成。

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

public class Child
{
   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

Namespace Child
    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.
End Namespace

父工作可以使用 TaskCreationOptions.DenyChildAttach 選項防止其他工作附加至父工作。 如需詳細資訊,請參閱附加與中斷連結的子工作

等候工作完成

System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> 類型會提供 Task.Wait 方法的數個多載,這些多載可讓您等候工作完成。 此外,靜態 Task.WaitAllTask.WaitAny 方法的多載可讓您等候工作陣列中的任一工作或所有工作完成。

您通常會因為下列其中一個原因而等候工作完成:

  • 主執行緒需要有工作的最終計算結果才能完成。

  • 您必須處理可能會從工作擲回的例外狀況。

  • 應用程式可能會在所有工作都已經完成執行之前結束。 例如,當 Main (應用程式進入點) 中的所有同步程式碼都已執行後,主控台應用程式就會終止。

下列範例顯示不含例外狀況處理的基本模式:

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

如需示範例外狀況處理的範例,請參閱例外狀況處理

有些多載可讓您指定逾時,有些多載則額外接受一個 CancellationToken 做為輸入參數,讓等候作業本身也可以取消 (不管是透過程式設計還是做為對使用者輸入的回應)。

等候工作時,也會隱含等候該工作之所有使用 TaskCreationOptions.AttachedToParent 選項建立的子系。 如果工作已完成,則 Task.Wait 會立即傳回。 Task.Wait 方法會擲回工作所引發的任何例外狀況,即使 Task.Wait 方法是在工作完成之後才呼叫也一樣。

組成工作

TaskTask<TResult> 類別提供數種方法來協助您撰寫多個工作。 這些方法會實作常見的模式,並更有效地利用 C#、Visual Basic 和 F# 所提供的非同步語言功能。 本節說明 WhenAllWhenAnyDelayFromResult 方法。

Task.WhenAll

Task.WhenAll 方法會以非同步方式來等候多個 TaskTask<TResult> 物件完成。 這會提供可讓您等候多組不一致工作的多載版本。 例如,您可以在單一方法呼叫中等候多個 TaskTask<TResult> 物件完成。

Task.WhenAny

Task.WhenAny 方法會以非同步方式來等候多個 TaskTask<TResult> 物件的其中一個完成。 和 Task.WhenAll 方法一樣,此方法也會提供可讓您等候多組不一致工作的多載版本。 WhenAny 方法在下列情況下特別有用:

  • 備援作業:請考慮能以多種方式執行的演算法或作業。 您可以使用 WhenAny 方法,選擇先完成的作業,然後取消其餘作業。

  • 交錯作業:您可以啟動必須完成的多個作業,並使用 WhenAny 方法以在每個作業完成時處理結果。 完成作業後,您可以開始一個或多個工作。

  • 節流作業:您可以使用 WhenAny 方法來限制並行作業數目,以擴充前一個案例。

  • 過期作業:您可以使用 WhenAny 方法來選取一或多個工作,以及在特定時間之後完成的工作,例如由 Delay 方法傳回的工作。 下一節將說明 Delay 方法。

Task.Delay

Task.Delay 方法會產生 Task 物件,這個物件會在指定的時間之後完成。 您可以使用此方法建置迴圈以輪詢資料、指定逾時、延遲處理使用者輸入等等。

Task(T).FromResult

使用 Task.FromResult 方法,您可以建立裝載預先計算結果的 Task<TResult> 物件。 當您執行會傳回 Task<TResult> 物件的非同步作業,而且該 Task<TResult> 物件的結果已計算時,這個方法很有用。 如需範例示範如何使用 FromResult 來擷取快取保留之非同步下載作業的結果,請參閱如何:建立經過預先計算的工作

處理工作中的例外狀況

當工作擲回一個或多個例外狀況時,這些例外狀況會包裝在 AggregateException 例外狀況中。 該例外狀況會傳播回與工作聯結的執行緒。 一般而言,這是等候工作完成或執行緒存取 Result 屬性的執行緒。 此行為會強制執行 .NET Framework 原則,即所有未處理的例外狀況預設都應該要終止處理序。 呼叫的程式碼可以在 try/catch 區塊中使用下列任何一個來處理例外狀況:

聯結的執行緒也可以藉由在工作被進行記憶體回收之前就存取 Exception 屬性,以處理例外狀況。 存取這個屬性,可防止未處理的例外狀況觸發當物件完成時,會終止處理序的例外狀況傳播行為。

如需例外狀況和工作的詳細資訊,請參閱例外狀況處理

取消工作

Task 類別支援合作式取消,且與 .NET Framework 4 中引進的 System.Threading.CancellationTokenSourceSystem.Threading.CancellationToken 類別完全整合。 System.Threading.Tasks.Task 類別中的許多建構函式都接受 CancellationToken 物件做為輸入參數。 許多 StartNewRun 多載也包含 CancellationToken 參數。

您可以先建立語彙基元,之後再使用 CancellationTokenSource 類別來發出取消要求。 請將語彙基元當做引數傳遞給 Task,並同時在您的使用者委派 (負責完成回應取消要求的動作) 中參考同一個語彙基元。

如需詳細資訊,請參閱工作取消如何:取消工作及其子系

TaskFactory 類別

TaskFactory 類別提供靜態方法,這些方法會封裝在建立和啟動工作及接續工作時常用的模式。

TaskFactory 類別或 Task 類別上會以靜態屬性的形式提供預設的 Task<TResult> 供人存取。 您也可以直接具現化 TaskFactory 並指定各種選項,包括 CancellationTokenTaskCreationOptions 選項、TaskContinuationOptions 選項或 TaskScheduler。 您在建立工作 Factory 時指定的任何選項都會套用至此 Factory 建立的所有工作,除非 Task 是以 TaskCreationOptions 列舉建立,在此情況下,工作選項會覆寫工作 Factory 的選項。

不含委派的工作

在某些情況下,您可能需要使用 Task 來封裝由外部元件 (而非您的使用者委派) 所執行的一些非同步作業。 如果作業是根據非同步程式設計模型 Begin/End 模式,您可以使用 FromAsync 方法。 如果不是根據這個模式,您可以使用 TaskCompletionSource<TResult> 物件將作業包裝在工作中,進而享有利用 Task 撰寫程式的一些優點。 例如,支援例外狀況傳播和接續。 如需詳細資訊,請參閱TaskCompletionSource<TResult>

自訂排程器

大部分的應用程式或程式庫開發人員並不在意工作會在哪一個處理器上執行、工作會如何將自己的成品與其他工作同步,或是工作會如何排定在 System.Threading.ThreadPool 上。 他們只要求工作能夠在主機電腦上盡可能有效率地執行。 如果您需要進一步控制排程細節,工作平行程式庫可讓您設定預設工作排程器上的某些設定,甚至可讓您提供自訂的排程器。 如需詳細資訊,請參閱TaskScheduler

TPL 提供數個新的公用類型,這些類型在平行處理和序列處理情節中會很有用。 其中包括 System.Collections.Concurrent 命名空間中的數個安全程序、快速且可調整的集合類別,以及數個新的同步處理類型。 例如 System.Threading.SemaphoreSystem.Threading.ManualResetEventSlim,這些是比其前置任務更有效率的特定工作負載類型。 .NET Framework 4 中還有其他新的類型 (例如 System.Threading.BarrierSystem.Threading.SpinLock) 可提供舊版所沒有的功能。 如需詳細資訊,請參閱適用於平行程式設計的資料結構

自訂工作類型

建議您不要繼承自 System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult>。 建議您改用 AsyncState 屬性,建立其他資料或狀態與 TaskTask<TResult> 物件的關聯。 您也可以使用擴充方法,擴充 TaskTask<TResult> 類別的功能。 如需擴充方法的詳細資訊,請參閱擴充方法擴充方法

如果必須繼承自 TaskTask<TResult>,則不可以使用 RunSystem.Threading.Tasks.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult>System.Threading.Tasks.TaskCompletionSource<TResult> 類別來建立自訂工作類型的執行個體。 您無法使用這些類別,因為這些類別只會建立 TaskTask<TResult> 物件。 此外,也不可以使用 TaskTask<TResult>TaskFactoryTaskFactory<TResult> 所提供的工作接續機制來建立自訂工作類型執行個體。 您無法使用這些機制,因為這些機制只會建立 TaskTask<TResult> 物件。

標題 描述
使用接續工作鏈結工作 說明接續的運作方式。
附加與中斷連結的子工作 說明附加的與中斷連結的子工作之間的差異。
工作取消 說明 Task 物件內建的取消支援。
例外狀況處理 說明並行執行緒上發生例外狀況時的處理方式。
如何:使用 Parallel.Invoke 執行平行作業 說明如何使用 Invoke
操作說明:傳回工作的值 說明如何從工作傳回值。
操作說明:取消工作及其子系 說明如何取消工作。
操作說明:建立經過預先計算的工作 描述如何使用 Task.FromResult 方法擷取保留在快取中之非同步下載作業的結果。
操作說明:使用平行工作周遊二進位樹狀 說明如何使用工作,在二進位樹狀目錄中周遊。
操作說明:解除包裝巢狀工作 示範如何使用 Unwrap 擴充方法。
資料平行處理原則 說明如何使用 ForForEach 建立資料的平行迴圈。
平行程式設計 .NET Framework 平行程式設計的最上層節點。

另請參閱