共用方式為


接續工作

更新:2010 年 6 月

在非同步程式設計中,一個非同步作業在完成時叫用第二個作業,並將資料傳遞給該作業,是很常見的情形。 傳統上會以回呼方法執行這些動作。 工作平行程式庫中由「接續工作」(Continuation Task) 提供此一相同功能。 接續工作 (也簡稱為「接續」) 是指在前項完成時,由另一項工作 (稱為「前項」(Antecedent)) 所叫用的非同步工作。

接續相對而言較容易使用,但其功能也頗為強大而具有彈性。 例如,您可以:

  • 將資料從前項傳遞至接續

  • 精確指定是否要叫用接續的條件

  • 在接續啟動之前加以取消,或在它執行時以合作方式加以取消

  • 提供有關如何排程接續的提示

  • 從相同的前項叫用多個接續

  • 在多個前項全部或有任何一個完成時,叫用一個接續

  • 將接續逐一鏈結成任意長度

  • 使用接續處理由前項所擲回的例外狀況

使用 Task.ContinueWith 方法建立接續。 下列範例說明基本模式 (在此省略例外狀況處理,以利說明)。

' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
                                                             Return DateTime.Today.DayOfWeek
                                                         End Function)
' The continuation. Its delegate takes the antecedent task
' as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
                                                             Return String.Format("Today is {0}", antecedent.Result)
                                                         End Function)
' Start the antecedent.
taskA.Start()

' Use the contuation's result.
Console.WriteLine(continuation.Result)
            // The antecedent task. Can also be created with Task.Factory.StartNew.
            Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

            // The continuation. Its delegate takes the antecedent task
            // as an argument and can return a different type.
            Task<string> continuation = taskA.ContinueWith((antecedent) =>
                {
                    return String.Format("Today is {0}.",
                                        antecedent.Result);
                });

            // Start the antecedent.
            taskA.Start();

            // Use the contuation's result.
            Console.WriteLine(continuation.Result);

您也可以建立會在任何或所有工作陣列完成時執行的多工接續,如下列範例所示。

Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 34
                                                     End Function)

Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 8
                                                     End Function)

Dim tasks() As Task(Of Integer) = {task1, task2}

Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
                                                           Dim answer As Integer = tasks(0).Result + tasks(1).Result
                                                           Console.WriteLine("The answer is {0}", answer)


                                                       End Sub)
task1.Start()
task2.Start()
continuation.Wait()
            Task<int>[] tasks = new Task<int>[2];
            tasks[0] = new Task<int>(() =>
            {
                // Do some work... 
                return 34;
            });

            tasks[1] = new Task<int>(() =>
            {
                // Do some work...
                 return 8;
            });

            var continuation = Task.Factory.ContinueWhenAll(
                            tasks,
                            (antecedents) =>
                            {
                                int answer = tasks[0].Result + tasks[1].Result;
                                Console.WriteLine("The answer is {0}", answer);
                            });

            tasks[0].Start();
            tasks[1].Start();
            continuation.Wait();

接續建立於 WaitingForActivation 狀態中,因此只能由其前項工作啟動。 若在使用者模式中對接續呼叫 Task.Start,將會引發 System.InvalidOperationException

接續本身是 Task,不會封鎖啟動它的執行緒。 使用 Wait 方法持續封鎖直到接續工作完成為止。

接續選項

當您建立單一工作接續時,您可以使用採用 System.Threading.Tasks.TaskContinuationOptions 列舉的 ContinueWith 多載,來指定前項工作會在哪些條件下啟動接續。 例如,您可以指定只有在前項執行完成時,或是前項在錯誤狀態下完成時等條件,才會執行接續。 如果在前項已就緒而可叫用接續時,條件不為 True,則接續會直接轉換成 Canceled 狀態,且自此無法再啟動。 如果您指定了任何 NotOn 或 OnlyOn 選項和多工接續,在執行階段中將會擲回例外狀況。

System.Threading.Tasks.TaskContinuationOptions 列舉也具有與 System.Threading.Tasks.TaskCreationOptions 列舉相同的選項。 在前述兩個列舉類型中,AttachedToParentLongRunningPreferFairness 的意義和值均相同。 這些選項可以與多工接續搭配使用。

下表列出 TaskContinuationOptions 中所有的值。

項目

描述

None

在未指定 TaskContinuationOptions 時指定預設行為。 當前項完成時,無論前項的最終狀態為何,都會排程接續。 如果該工作為子工作,則會建立為中斷連結的巢狀工作。

PreferFairness

指定會排程接續,且先排程的工作先執行的機率較高,後排程的工作則可能較晚執行。

LongRunning

指定接續會是長時間執行的繁複作業。 這會提示 System.Threading.Tasks.TaskScheduler 可能會發生過度訂閱的狀況。

AttachedToParent

指定接續 (如果是子工作) 會附加至工作階層中的父系。 只有在接續的前項也是子工作時,該接續才是子工作。

NotOnRanToCompletion

指定當接續的前項執行完成時,不應該排程接續。

NotOnFaulted

指定當接續的前項擲回未處理的例外狀況時,不應該排程接續。

NotOnCanceled

指定當接續的前項取消時,不應該排程接續。

OnlyOnRanToCompletion

指定只有在前項執行完成時,始應排程接續。

OnlyOnFaulted

指定只有在接續的前項擲回未處理的例外狀況時,始應排程接續。 當您使用 OnlyOnFaulted 選項時,前項中的 Exception 屬性必定不是 null。 您可以使用該屬性來攔截例外狀況,並查看是哪一種例外狀況導致工作錯誤。 如果您未存取 Exception 屬性,則會形成未處理的例外狀況。 此外,如果您嘗試存取已取消或已發生錯誤之工作的 Result 屬性,將會引發新的例外狀況。

OnlyOnCanceled

指定只有在接續的前項以 Canceled 狀態完成時,始應排程接續。

ExecuteSynchronously

適用於非常短期執行的接續。 指定接續最好在造成前項轉入最終狀態的同一個執行緒上執行。 如果建立接續時前項已完成,則系統會嘗試在建立接續的執行緒上執行接續。 如果前項的 CancellationTokenSource 在 finally (Visual Basic 中為 Finally) 區塊中遭處置,則會在該 finally 區塊中執行具有這個選項的接續。

將資料傳遞至接續

前項的參考會以引數的形式傳遞至接續的使用者委派。 如果前項為 System.Threading.Tasks.Task<TResult>,而工作已執行完成,則接續可存取工作的 Task<TResult>.Result 屬性。 使用多工接續和 Task.WaitAll 方法時,引數將是前項的陣列。 當您使用 Task.WaitAny 時,引數將是第一個完成的前項。

在工作完成前,Task<TResult>.Result 都將處於封鎖狀態。 但如果工作已取消或發生錯誤,則 Result 會在您的程式碼嘗試存取工作時擲回例外狀況。 您可以使用 OnlyOnRanToCompletion 選項來避免這個問題,如下列範例所示。

            Dim aTask = Task(Of Integer).Factory.StartNew(Function()
                                                              Return 54
                                                          End Function)
            Dim bTask = aTask.ContinueWith(Sub(antecedent)
                                               Console.WriteLine("continuation {0}", antecedent.Result)
                                           End Sub,
                                           TaskContinuationOptions.OnlyOnRanToCompletion)

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
},
    TaskContinuationOptions.OnlyOnRanToCompletion);

如果即使前項未完成執行,您也要讓接續執行,則您必須防止例外狀況出現。 可行的方法之一是測試前項的狀態,且僅在狀態不是 FaultedCanceled 時嘗試存取 Result。 您也可以檢查前項的 Exception 屬性。 如需詳細資訊,請參閱例外處理 (工作平行程式庫)

取消接續

接續會在下列情況下進入 Canceled 狀態:

如果要讓接續在其前項已取消時無法執行,請在建立接續時指定 NotOnCanceled 選項。

如果工作和其接續代表相同的邏輯作業的兩個部分,則您可以傳遞相同的取消語彙基元到這兩項工作,如下列範例所示。

Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
                         Dim ct As CancellationToken = cts.Token
                         While someCondition = True
                             ct.ThrowIfCancellationRequested()
                             ' Do the work here...
                             ' ...
                         End While
                     End Sub,
                     cts.Token
                     )

Dim task2 = task1.ContinueWith(Sub(antecedent)
                                   Dim ct As CancellationToken = cts.Token
                                   While someCondition = True
                                       ct.ThrowIfCancellationRequested()
                                       ' Do the work here
                                       ' ...
                                   End While
                               End Sub,
                               cts.Token)
task1.Start()
' ...
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Task task = new Task(() =>
{
    CancellationToken ct = cts.Token;
    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token
    );

Task task2 = task.ContinueWith((antecedent) =>
{
    CancellationToken ct = cts.Token;

    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token);

task.Start();
//...

// Antecedent and/or continuation will 
// respond to this request, depending on when it is made.
cts.Cancel();

如果前項未取消,則仍可使用語彙基元取消接續。 如果前項已取消,則接續將不會啟動。

接續在進入 Canceled 狀態後,可能會對後續的接續造成影響,視這些接續的指定 TaskContinuationOptions 而定。

已處置的接續將不會啟動。

接續和子工作

在前項及其所有的附加子工作完成後,接續才會執行。 接續不會等候中斷連結的子工作完成。 前項工作的最終狀態取決於任何附加子工作的最終狀態。 中斷連結之子工作的狀態並不會影響到父代。 如需詳細資訊,請參閱巢狀工作和子工作

處理接續所擲回的例外狀況

前項與接續的關聯性並不是父子關聯性。 接續所擲回的例外狀況不會傳播到前項。 因此,接續所擲回的例外狀況和任何其他工作中的例外狀況,在處理上並無不同,如下所示。

  1. 使用 WaitWaitAllWaitAny 方法或是一般的等同項目,等候接續作業。 您可以在相同的 try (在 Visual Basic 中為 Try) 陳述式中等候前項及其接續,如下列範例所示。
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
                                                  Return 54
                                              End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
                                          Console.WriteLine("continuation {0}", antecedent.Result)
                                          Throw New InvalidOperationException()
                                      End Sub)

Try
    task1.Wait()
    continuation.Wait()
Catch ae As AggregateException
    For Each ex In ae.InnerExceptions
        Console.WriteLine(ex.Message)
    Next
End Try

Console.WriteLine("Exception handled. Let's move on.")
var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
    throw new InvalidOperationException();
});

try
{
    t.Wait();
    c.Wait();
}

catch (AggregateException ae)
{
    foreach(var e in ae.InnerExceptions)
        Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
  1. 使用第二個接續觀察第一個接續的 Exception 屬性。 如需詳細資訊,請參閱例外處理 (工作平行程式庫)HOW TO:處理工作擲回的例外狀況

  2. 如果接續是子工作,並且是以 AttachedToParent 選項建立的,則其例外狀況將會由父代傳播回呼叫端執行緒,如同任何其他附加子工作中的情形。 如需詳細資訊,請參閱巢狀工作和子工作

請參閱

概念

工作平行程式庫

變更記錄

日期

記錄

原因

2010 年 6 月

加入有關接續非同步行為的注意事項。

客戶回函。