例外處理 (工作平行程式庫)
在工作內執行之使用者程式碼所擲回的未處理例外狀況會傳播回聯結的執行緒 (但本主題稍後描述的特定案例則不適用)。 當您使用其中一個靜態或執行個體 Task.Wait 或 Task<TResult>.Wait 方法時,會傳播例外狀況,而您可以將呼叫置於 try-catch 陳述式中來處理這些例外狀況。 如果工作是已附加子工作的父代,或是您正在等候多個工作完成,則可能會擲回多個例外狀況。 為了將所有例外狀況都傳播回呼叫端執行緒,Task 基礎結構會將這些例外狀況包裝在一個 AggregateException 執行個體中。 AggregateException 具有 InnerExceptions 屬性,您可以列舉這個屬性來檢查所有擲回的原始例外狀況,並且個別處理 (或不處理) 每個例外狀況。 即使只有擲回一個例外狀況,仍然會將這個例外狀況包裝在 AggregateException 中。
Dim task1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("I'm bad, but not too bad!")
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
' Assume we know what's going on with this particular exception.
' Rethrow anything else. AggregateException.Handle provides
' another way to express this. See later example.
For Each ex In ae.InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
End Try
var task1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("I'm bad, but not too bad!");
});
try
{
task1.Wait();
}
catch (AggregateException ae)
{
// Assume we know what's going on with this particular exception.
// Rethrow anything else. AggregateException.Handle provides
// another way to express this. See later example.
foreach (var e in ae.InnerExceptions)
{
if (e is MyCustomException)
{
Console.WriteLine(e.Message);
}
else
{
throw;
}
}
}
只攔截 AggregateException 而不觀察任何內部例外狀況,可讓您避免遇到未處理的例外狀況。 不過並不建議您這樣做,因為這就像是在非平行案例中攔截基底 Exception 型別。 如果只攔截例外狀況而不採取特定動作以從例外狀況復原,您的程式可能會處於不定狀態。
如果您不等候傳播例外狀況的工作完成,或是未存取其 Exception 屬性,則當工作被進行記憶體回收時,會依照 .NET 例外狀況原則提昇該例外狀況。
如果可以將每個例外狀況個別擲回給聯結的執行緒,則引發例外狀況之後,工作仍可能會繼續處理某些項目。
注意事項 |
---|
啟用 "Just My Code" 時,Visual Studio 有時候會在擲回例外狀況的程式碼行處中斷,並顯示「使用者程式碼未處理的例外狀況」之類的錯誤訊息。這是良性的錯誤。您可以按 F5 繼續,並看到這些範例所示的例外處理行為。若要防止 Visual Studio 在遇到第一個錯誤時就中斷,只要取消選取 [工具]、[選項]、[偵錯]、[一般] 下的 [Just My Code] 核取方塊即可。 |
附加的子工作和巢狀 AggregateException
如果工作中附加的子工作擲回例外狀況,則該例外狀況會先包裝在 AggregateException 中再傳播給父工作,而父工作會將該例外狀況包裝在自己的 AggregateException 中再傳播回呼叫端執行緒。 在這種情況下,在 Task.Wait 或 Task<TResult>.Wait 或 WaitAny 或 WaitAll 方法攔截的 AggregateException 的 AggregateException().InnerExceptions 屬性會包含一個或多個 AggregateException 執行個體,而不是包含造成錯誤的原始例外狀況。 若不想逐一查看巢狀 AggregateExceptions,您可以使用 Flatten() 方法來移除所有巢狀的 AggregateExceptions,讓 AggregateException() InnerExceptions 屬性只包含原始例外狀況。 在下列範例中,只用一個迴圈就將巢狀 AggregateException 執行個體進行簡維和處理。
' task1 will throw an AE inside an AE inside an AE
Dim task1 = Task.Factory.StartNew(Sub()
Dim child1 = Task.Factory.StartNew(Sub()
Dim child2 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("Attached child2 faulted.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub,
TaskCreationOptions.AttachedToParent)
' Uncomment this line to see the exception rethrown.
' throw new MyCustomException("Attached child1 faulted.")
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
'or like this:
' ae.Flatten().Handle(Function(e)
' Return TypeOf (e) Is MyCustomException
' End Function)
End Try
// task1 will throw an AE inside an AE inside an AE
var task1 = Task.Factory.StartNew(() =>
{
var child1 = Task.Factory.StartNew(() =>
{
var child2 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("Attached child2 faulted.");
},
TaskCreationOptions.AttachedToParent);
// Uncomment this line to see the exception rethrown.
// throw new MyCustomException("Attached child1 faulted.");
},
TaskCreationOptions.AttachedToParent);
});
try
{
task1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.Flatten().InnerExceptions)
{
if (e is MyCustomException)
{
// Recover from the exception. Here we just
// print the message for demonstration purposes.
Console.WriteLine(e.Message);
}
else
{
throw;
}
}
// or ...
// ae.Flatten().Handle((ex) => ex is MyCustomException);
}
由中斷連結的子工作所擲回的例外狀況
根據預設,子工作建立時會是中斷連結的狀態。 由中斷連結的工作所擲回的例外狀況必須由直屬父代處理或重新擲回。這些例外狀況不會像由已附加的子工作所擲回的例外狀況一樣傳播回呼叫端執行緒。 最上層的父代可以手動將由中斷連結的子系所擲回的例外狀況重新擲回,以將該例外狀況包裝在 AggregateException 中並傳播回聯結的執行緒。
Dim task1 = Task.Factory.StartNew(Sub()
Dim nestedTask1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("Nested task faulted.")
End Sub)
' Here the exception will be escalated back to joining thread.
' We could use try/catch here to prevent that.
nestedTask1.Wait()
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf (ex) Is MyCustomException Then
' Recover from the exception. Here we just
' print the message for demonstration purposes.
Console.WriteLine(ex.Message)
End If
Next
End Try
var task1 = Task.Factory.StartNew(() =>
{
var nested1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("Nested task faulted.");
});
// Here the exception will be escalated back to joining thread.
// We could use try/catch here to prevent that.
nested1.Wait();
});
try
{
task1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.Flatten().InnerExceptions)
{
if (e is MyCustomException)
{
// Recover from the exception. Here we just
// print the message for demonstration purposes.
Console.WriteLine(e.Message);
}
}
}
即使您使用接續工作來觀察子工作中的例外狀況,仍然必須讓父工作觀察該例外狀況。
表示合作式取消的例外狀況
當工作中的使用者程式碼回應取消要求時,正確的程序是擲回 OperationCanceledException 並傳入用以傳達要求的取消語彙基元。 工作執行個體在嘗試傳播例外狀況之前,會先將例外狀況中的語彙基元與自己建立時所分配到的語彙基元進行比較。 如果這些語彙基元相同,則工作會傳播以 AggregateException 包裝的 TaskCanceledException,而檢查內部例外狀況時即可看到後面這個例外狀況。 不過,如果聯結的執行緒沒有在等候工作完成,即不會傳播這個特定的例外狀況。 如需詳細資訊,請參閱 工作取消。
Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token
Dim task1 = Task.Factory.StartNew(Sub()
Dim ct As CancellationToken = token
While someCondition = True
' Do some work...
Thread.SpinWait(500000)
ct.ThrowIfCancellationRequested()
End While
End Sub,
token)
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task1 = Task.Factory.StartNew(() =>
{
CancellationToken ct = token;
while (someCondition)
{
// Do some work...
Thread.SpinWait(50000);
ct.ThrowIfCancellationRequested();
}
},
token);
// No waiting required.
使用 Handle 方法篩選內部例外狀況
使用 Handle() 方法時,您無須使用任何進一步的邏輯,即可篩選出可視為「已處理」的例外狀況。 在提供給 Handle() 的使用者委派中,您可以檢查例外狀況型別、其 Message() 屬性,或是任何其他可讓您判斷這是否為良性例外狀況的資訊。 在 Handle() 傳回之後,會立即將任何讓委派傳回 false 的例外狀況放在新的 AggregateException 執行個體中重新擲回。
下列程式碼片段會使用 foreach 迴圈來查看內部例外狀況。
For Each ex In ae.InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
foreach (var e in ae.InnerExceptions)
{
if (e is MyCustomException)
{
Console.WriteLine(e.Message);
}
else
{
throw;
}
}
下列程式碼片段顯示在功能上相當於 Handle() 方法的用法。
ae.Handle(Function(ex)
Return TypeOf (ex) Is MyCustomException
End Function)
ae.Handle((ex) =>
{
return ex is MyCustomException;
});
使用 Task.Exception 屬性觀察例外狀況
如果工作完成時處於 Faulted 狀態,您可以檢查其 Exception 屬性,以查出究竟是哪個例外狀況造成錯誤。 觀察 Exception 屬性的一個好方法,就是使用只有在前項工作失敗時才會執行的接續工作,如下列範例所示。
Dim task1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("task1 faulted.")
End Sub).ContinueWith(Sub(t)
Console.WriteLine("I have observed a {0}", _
t.Exception.InnerException.GetType().Name)
End Sub,
TaskContinuationOptions.OnlyOnFaulted)
var task1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
{
Console.WriteLine("I have observed a {0}",
t.Exception.InnerException.GetType().Name);
},
TaskContinuationOptions.OnlyOnFaulted);
在實際的應用程式中,接續工作的委派可能會記錄例外狀況的詳細資訊,並且可能繁衍新的工作以從例外狀況復原。
UnobservedTaskException 事件
在某些情況下 (例如當裝載了未受信任的外掛程式時),可能會經常發生良性例外狀況,而這樣您會很難手動觀察全部的良性例外狀況。 在這些情況下,您可以處理 TaskScheduler.UnobservedTaskException 事件。 您可以使用您的處理常式中所傳遞的 System.Threading.Tasks.UnobservedTaskExceptionEventArgs 執行個體,防止將未觀察的例外狀況傳播回聯結的執行緒。