2014-09-05 120 views
7

我下面舉個例子:(也請閱讀代碼中的註釋,因爲它會更有意義)異步任務拋出異常,如果沒有等待?

public async Task<Task<Result>> MyAsyncMethod() 
{ 
    Task<Result> resultTask = await _mySender.PostAsync(); 
    return resultTask; 

    // in real-life case this returns to a different assembly which I can't change 
    // but I need to do some exception handling on the Result in here 
} 

假設的PostAsync方法_ mySender看起來是這樣的:

public Task<Task<Result>> PostAsync() 
{ 
    Task<Result> result = GetSomeTask(); 
    return result; 
} 

的問題是:

因爲我不在等待中的實際Result,並且如果PostAsync方法拋出異常,在哪個上下文是要拋出和處理的異常?

有什麼辦法可以處理我的程序集中的異常嗎?

我很驚訝,當我試圖改變MyAsyncMethod到:

public async Task<Task<Result>> MyAsyncMethod() 
{ 
    try 
    { 
    Task<Result> resultTask = await _mySender.PostAsync(); 
    return resultTask; 
    } 
    catch (MyCustomException ex) 
    { 
    } 
} 

異常被抓到這裏來,事件是否有對應的實際結果沒有任何的await。碰巧PostAsync的結果已經可用,並且在這種情況下拋出異常嗎?

是否可以使用ContinueWith來處理當前類中的異常?例如:

public async Task<Task<Result>> MyAsyncMethod() 
{ 
    Task<Result> resultTask = await _mySender.PostAsync(); 
    var exceptionHandlingTask = resultTask.ContinueWith(t => { handle(t.Exception)}, TaskContinuationOptions.OnlyOnFaulted); 
    return resultTask; 
} 
+0

您可能想看看TPL的[異常處理](http://msdn.microsoft.com/en-us/library/dd997415(v = vs.110).aspx)頁面。 – 2014-09-05 17:56:52

回答

20

這是一個很大的問題打包成一個單一的「問題」,但行...

異步任務拋出異常,如果沒有等待?

未觀察到的任務異常由TaskScheduler.UnobservedTaskException事件引發。這個事件是「最終」提出的,因爲任務在其異常被認爲未處理之前實際上必須被垃圾收集。

由於我不等待MyAsyncMethod中的實際結果,並且如果PostAsync方法引發異常,哪個上下文是要引發和處理的異常?

使用該async修改,並返回一個Task會把所有的異常對返回Task任何方法。

有沒有什麼辦法可以處理我的程序集中的異常?

是的,你可以更換返回任務,是這樣的:

async Task<Result> HandleExceptionsAsync(Task<Result> original) 
{ 
    try 
    { 
    return await original; 
    } 
    catch ... 
} 

public async Task<Task<Result>> MyAsyncMethod() 
{ 
    Task<Result> resultTask = await _mySender.PostAsync(); 
    return HandleExceptionsAsync(resultTask); 
} 

我很驚訝,當我試圖改變MyAsyncMethod爲[同步返回內部任務]異常被抓到這裏來即使沒有等待實際結果。

這實際上意味着您調用的方法不是async Task,如代碼示例所示。這是一種非返回方法,當這些方法之一拋出一個異常時,它就像其他任何異常一樣被處理(即它直接通過調用堆棧;它不放在返回的Task上)。

是否可以使用ContinueWith來處理當前類中的異常?

是的,但await更乾淨。

+0

嗨斯蒂芬,謝謝你的回答。你是對的這個例子是不正確的,該方法是一個非異步返回Task.Going試試你的建議謝謝 – 2014-09-05 21:37:25

+0

嘿@Dan Dinu,我知道這是一段時間,但你爲什麼不接受這樣的好答案? – 2015-08-31 07:55:26

6

我對Task使用通用錯誤處理的擴展方法。這提供了一種記錄所有錯誤的方法,並在發生錯誤時執行自定義操作。

public static async void ErrorHandle(this Task task, Action action = null) 
{ 
    try 
    { 
     await task.ConfigureAwait(false); 
    } 
    catch (Exception e) 
    { 
     Log.Error(e); 
     if (action != null) 
      action(); 
    } 
} 

我傾向於使用它時,我做了「射後不理」 Task

Task.Run(() => ProcessData(token), token).ErrorHandle(OnError); 
+0

這不回答所問的問題。另外,使用TPL的主要原因之一是它的錯誤處理功能非常強大。爲什麼你會用自己的方式回到錯誤處理的回調風格呢? – Servy 2014-09-05 20:05:30

+0

我可以看到它用於消防和忘記行動。例如,當我執行一組可以並行運行的任務時,我不想等待每個任務,但仍然希望拋出異常(即記錄日誌)。 – 2017-04-18 14:30:15

2

由於我不等待MyAsyncMethod中的實際結果,並且如果PostAsync方法引發異常,在哪種情況下異常將被拋出和處理?

如果你不await任何的任務,在你的代碼或不分配的延續,行爲可能取決於你使用.NET Framework的版本不同。在這兩種情況下,返回Task會吞下例外,將出現差異,在最後確定:

  1. .NET 4.0 - 終結線程將重新拋出異常吞噬。如果沒有註冊全局異常處理程序,它將終止進程

  2. .NET 4.5及以上版本 - 異常將被吞噬並且不被注意。

在這兩種情況下TaskScheduler.UnobservedTaskException事件將觸發:

當故障任務的未觀測到的異常即將觸發異常升級策略,其中,默認情況下,將終止該進程時發生。

當非async任務返回方法執行同步異常立即將會傳播,這就是爲什麼你捕獲異常,而不使用await,但你一定要將取決於在你的代碼。

有什麼辦法,我可以在我的組裝

是的,你可以處理異常。我會建議你await你在你的程序集中執行的任務。

沒有理由要使用async修改,如果你不等待任何東西:

public Task<Result> PostAsync() 
{ 
    return GetSomeTask(); 
} 

然後,您可以awaitPostAsync,趕上有例外:

public async Task<Result> MyAsyncMethod() 
{ 
    try 
    { 
     // No need to use Task<Result> as await will unwrap the outter task 
     return await _mySender.PostAsync(); 
    } 
    catch (MyCustomException e) 
    { 
      // handle here 
    } 
} 

你甚至可以進一步修改此代碼並刪除async關鍵字,並可能捕獲異常甚至更高的調用堆棧到調用MyAsyncMethod的方法。

+1

如果異步在'async'方法中拋出,即使在第一個'await'之前,它仍然會被包裝在返回的Task中。它不會導致實際的方法調用拋出異常,即使它在同步運行時達到該異常。 – Servy 2014-09-05 20:38:33

+0

對,它是非異步'任務'返回方法。我修改了。 – 2014-09-05 20:47:12