簡體   English   中英

ContinueWith TaskContinuationOptions.OnlyOnFaulted似乎沒有捕獲從啟動任務拋出的異常

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM