[英]TaskContinuationOptions.OnlyOnFaulted vs try catch
[英]ContinueWith TaskContinuationOptions.OnlyOnFaulted does not seem to catch an exception thrown from a started task
我正在嘗試捕獲使用ContinueWith和OnlyOnFaulted的任務方法拋出的異常,如下所示。 但是,當我嘗試運行此代碼時,我得到一個未處理的異常。
因為我已經處理了異常,所以我希望該任務能夠完成。 但Task.Wait()運行到AggregateException。
var taskAction = new Action(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
如果我在下面的任務方法中處理異常,一切都會像我預期的那樣正常。 任務運行完成,異常被記錄,等待也成功。
var taskAction = new Action(() =>
{
try
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception ex)
{
Console.WriteLine("Catching the exception in the Action catch block only");
}
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x=> Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
我的問題是:我是否正確使用了OnlyOnFaulted
,或者在任務方法本身處理異常總是更好嗎? 即使任務遇到異常,我希望主線程繼續。 另外,我想從任務方法中記錄該異常。
注意:我必須等待任務方法完成后再繼續(有或沒有錯誤)。
總結(到目前為止我的理解)
如果處理來自Task的異常,即如果wait或await捕獲異常,那么異常將傳播到continuetask onfaulted。 甚至在任務方法中也可以捕獲異常並消耗\\處理。
try
{
t.wait();
}
catch(Exception e)
{
LogError(e);
}
在調用LogError之前的上述情況中,執行與主任務的onfaulted相關聯的繼續任務。
首先,您沒有正確使用OnlyOnFaulted
。 當你在任務上使用ContinueWith
,你並沒有真正改變那個任務, 你會得到一個任務延續 (在你的情況下你忽略了)。
如果原始任務出現故障(即在其中拋出異常) ,它將保持故障 (因此在其上調用Wait()
將始終重新拋出異常)。 但是,在任務出現故障並處理異常后,將繼續運行。
這意味着在您的代碼中您確實處理了異常,但您也在使用Wait()
重新拋出異常。 正確的代碼應如下所示:
Task originalTask = Task.Run(() => throw new Exception());
Task continuationTask = originalTask.ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
continuationTask.Wait()
// Both tasks completed. No exception rethrown
現在,正如Yuval Itzchakov指出的那樣,你可以在任何你想要的地方處理異常,但是如果你使用async-await
來異步等待(如果可以的話)(不能在Main
)而不是使用Wait()
阻塞它會更好:
try
{
await originalTask;
}
catch (Exception e)
{
// handle exception
}
我正確使用
TaskContinutationOptions.OnlyOnFaulted
還是總是更好地處理任務方法本身的異常? 即使任務遇到異常,我希望主線程繼續。
您可以在內部或外部處理異常。 這是一個偏好問題,通常取決於用例。
請注意,您沒有做的一件事是保留對延續的引用。 你在原始任務上使用Task.Wait
來傳播異常,而不管你有一個處理它的延續。
困擾我的一件事是你正在使用Task.Wait
,它同步等待而不是await
異步等待 。 這就是AggregationException
的原因。 更重要的是,你不應該阻止異步操作,因為這會導致你陷入一個你可能不想去的rabit漏洞,伴隨着各種同步上下文問題。
我個人會做的是在ContinueWith
使用await
,因為它的冗長選項。 另外,我在Task.Factory.StartNew上使用Task.Run :
var task = Task.Run(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
}
// Do more stuff here until you want to await the task.
try
{
await task;
}
catch (InvalidOperationException ioe)
{
// Log.
}
最初的問題恰好是在一個單獨的線程上運行taskAction
。 情況可能並非總是如此。
i3arnon的答案很好地解決了這個問題。 如果我們不想使用單獨的線程怎么辦? 如果我們想簡單地啟動任務,同步運行它,直到我們達到IO或延遲,然后繼續我們自己的業務。 只有在最后,我們才會等待任務完成。
我們如何通過重新拋出異常等待任務? 我們將使用一個空的延續,而不是將其包裝在Task.Run()
,當任務因任何原因完成時,它將始終成功。
// Local function that waits a moment before throwing
async Task ThrowInAMoment()
{
await Task.Delay(1000);
Console.WriteLine("Task waited for a sec");
throw new Exception("Throwing for example");
}
// Start the task without waiting for it to complete
var t = ThrowInAMoment();
// We reach this line as soon as ThrowInAMoment() can no longer proceed synchronously
// This is as soon as it hits its "await Delay(1000)"
// Handle exceptions in the task
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
// Continue about our own business
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
// Now we want to wait for the original task to finish
// But we do not care about its exceptions, as they are already being handled
// We can use ContinueWith() to get a task that will be in the completed state regardless of HOW the original task finished (RanToCompletion, Faulted, Canceled)
await t.ContinueWith(task => {});
// Could use .Wait() instead of await if you want to wait synchronously for some reason
看起來你走在正確的軌道上。 我運行了你的代碼,得到了相同的結果。 我的建議是直接從動作中使用try / catch。 這將使您的代碼更清晰,並允許您記錄它來自哪個線程的內容,這在連續路徑中可能不同。
var taskAction = new Action(() =>
{
try{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception e)
{
Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ e);
}
});
Task t = Task.Factory.StartNew(taskAction);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
await t;
[編輯]刪除了外部異常處理程序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.