接續工作
更新: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 列舉相同的選項。 在前述兩個列舉類型中,AttachedToParent、LongRunning 和 PreferFairness 的意義和值均相同。 這些選項可以與多工接續搭配使用。
下表列出 TaskContinuationOptions 中所有的值。
項目 |
描述 |
---|---|
在未指定 TaskContinuationOptions 時指定預設行為。 當前項完成時,無論前項的最終狀態為何,都會排程接續。 如果該工作為子工作,則會建立為中斷連結的巢狀工作。 |
|
指定會排程接續,且先排程的工作先執行的機率較高,後排程的工作則可能較晚執行。 |
|
指定接續會是長時間執行的繁複作業。 這會提示 System.Threading.Tasks.TaskScheduler 可能會發生過度訂閱的狀況。 |
|
指定接續 (如果是子工作) 會附加至工作階層中的父系。 只有在接續的前項也是子工作時,該接續才是子工作。 |
|
指定當接續的前項執行完成時,不應該排程接續。 |
|
指定當接續的前項擲回未處理的例外狀況時,不應該排程接續。 |
|
指定當接續的前項取消時,不應該排程接續。 |
|
指定只有在前項執行完成時,始應排程接續。 |
|
指定只有在接續的前項擲回未處理的例外狀況時,始應排程接續。 當您使用 OnlyOnFaulted 選項時,前項中的 Exception 屬性必定不是 null。 您可以使用該屬性來攔截例外狀況,並查看是哪一種例外狀況導致工作錯誤。 如果您未存取 Exception 屬性,則會形成未處理的例外狀況。 此外,如果您嘗試存取已取消或已發生錯誤之工作的 Result 屬性,將會引發新的例外狀況。 |
|
指定只有在接續的前項以 Canceled 狀態完成時,始應排程接續。 |
|
適用於非常短期執行的接續。 指定接續最好在造成前項轉入最終狀態的同一個執行緒上執行。 如果建立接續時前項已完成,則系統會嘗試在建立接續的執行緒上執行接續。 如果前項的 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);
如果即使前項未完成執行,您也要讓接續執行,則您必須防止例外狀況出現。 可行的方法之一是測試前項的狀態,且僅在狀態不是 Faulted 或 Canceled 時嘗試存取 Result。 您也可以檢查前項的 Exception 屬性。 如需詳細資訊,請參閱例外處理 (工作平行程式庫)。
取消接續
接續會在下列情況下進入 Canceled 狀態:
當它在回應取消要求期間擲回 OperationCanceledException 時。 與所有工作相同,例外狀況中如果包含傳遞至接續的相同語彙基元,將會被視為確認合作式取消。
在接續執行前,System.Threading.CancellationToken 以引數的形式傳遞至接續,且語彙基元的 IsCancellationRequested 屬性為 true (True) 時。 在這種情況下接續將不會啟動,而會直接切換到 Canceled 狀態。
當接續因其 TaskContinuationOptions 所設定的條件不符,而未曾執行時。 例如,如果工作進入 Faulted 狀態,則其由 NotOnFaulted 選項所建立的接續將會切換至 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 而定。
已處置的接續將不會啟動。
接續和子工作
在前項及其所有的附加子工作完成後,接續才會執行。 接續不會等候中斷連結的子工作完成。 前項工作的最終狀態取決於任何附加子工作的最終狀態。 中斷連結之子工作的狀態並不會影響到父代。 如需詳細資訊,請參閱巢狀工作和子工作。
處理接續所擲回的例外狀況
前項與接續的關聯性並不是父子關聯性。 接續所擲回的例外狀況不會傳播到前項。 因此,接續所擲回的例外狀況和任何其他工作中的例外狀況,在處理上並無不同,如下所示。
- 使用 Wait、WaitAll 或 WaitAny 方法或是一般的等同項目,等候接續作業。 您可以在相同的 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.");
使用第二個接續觀察第一個接續的 Exception 屬性。 如需詳細資訊,請參閱例外處理 (工作平行程式庫) 和 HOW TO:處理工作擲回的例外狀況。
如果接續是子工作,並且是以 AttachedToParent 選項建立的,則其例外狀況將會由父代傳播回呼叫端執行緒,如同任何其他附加子工作中的情形。 如需詳細資訊,請參閱巢狀工作和子工作。
請參閱
概念
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2010 年 6 月 |
加入有關接續非同步行為的注意事項。 |
客戶回函。 |