简体   繁体   English

ContinueWith TaskContinuationOptions.OnlyOnFaulted似乎没有捕获从启动任务抛出的异常

[英]ContinueWith TaskContinuationOptions.OnlyOnFaulted does not seem to catch an exception thrown from a started task

I'm trying to catch an exception thrown from a task method using ContinueWith and OnlyOnFaulted like below. 我正在尝试捕获使用ContinueWith和OnlyOnFaulted的任务方法抛出的异常,如下所示。 However I get an unhandled exception while I try to run this code. 但是,当我尝试运行此代码时,我得到一个未处理的异常。

I'd like the task to run to completion since I have handled the exception already. 因为我已经处理了异常,所以我希望该任务能够完成。 But Task.Wait() runs into AggregateException. 但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();    

If I handle the exception in the task method like below, everything will go normal as I expect. 如果我在下面的任务方法中处理异常,一切都会像我预期的那样正常。 Task Runs Into Completion, Exception gets logged and Wait succeeds also. 任务运行完成,异常被记录,等待也成功。

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();    

My question is: Am I using the OnlyOnFaulted correctly or is it always better to handle the exception in the task method itself? 我的问题是:我是否正确使用了OnlyOnFaulted ,或者在任务方法本身处理异常总是更好吗? I would like the main thread to continue even if task runs into exception. 即使任务遇到异常,我希望主线程继续。 Also, I want to log that exception from task method. 另外,我想从任务方法中记录该异常。

Note: I have to wait for the task method to complete before going further (with or without errors). 注意:我必须等待任务方法完成后再继续(有或没有错误)。

To summarize(my understanding so far) 总结(到目前为止我的理解)

If exception from Task is handled ie if wait or await catches the exception then the exception would be propagated to continuedtask onfaulted. 如果处理来自Task的异常,即如果wait或await捕获异常,那么异常将传播到continuetask onfaulted。 Exception can be caught even in the task method and consumed\\handled. 甚至在任务方法中也可以捕获异常并消耗\\处理。

try
{ 
   t.wait();
}
catch(Exception e)
{
   LogError(e);
}

In above case before LogError gets called, continued task associated with the main task's onfaulted gets executed. 在调用LogError之前的上述情况中,执行与主任务的onfaulted相关联的继续任务。

First of all, you aren't using OnlyOnFaulted correctly. 首先,您没有正确使用OnlyOnFaulted When you use ContinueWith on a task you don't really change that task, you get back a task continuation (which in your case you disregard). 当你在任务上使用ContinueWith ,你并没有真正改变那个任务, 你会得到一个任务延续 (在你的情况下你忽略了)。

If the original task faulted (ie had an exception thrown inside it) it would stay faulted (so calling Wait() on it would always rethrow the exception). 如果原始任务出现故障(即在其中抛出异常) ,它将保持故障 (因此在其上调用Wait()将始终重新抛出异常)。 The continuation however would run after the task faulted and handle the exception. 但是,在任务出现故障并处理异常后,将继续运行。

That means that in your code you do handle the exception, but you're also rethrowing it with Wait() . 这意味着在您的代码中您确实处理了异常,但您也在使用Wait()重新抛出异常。 The correct code should look like this: 正确的代码应如下所示:

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

Now, as Yuval Itzchakov pointed out, you can handle the exception wherever you want but it would better if you were utilizing async-await to wait asynchronously if you can (you can't in Main ) instead of blocking with Wait() : 现在,正如Yuval Itzchakov指出的那样,你可以在任何你想要的地方处理异常,但是如果你使用async-await来异步等待(如果可以的话)(不能在Main )而不是使用Wait()阻塞它会更好:

try
{
    await originalTask;
}
catch (Exception e)
{
    // handle exception
}

Am I using the TaskContinutationOptions.OnlyOnFaulted correctly or is it always better to handle the exception in the task method itself? 我正确使用TaskContinutationOptions.OnlyOnFaulted还是总是更好地处理任务方法本身的异常? I would like the main thread to continue even if task runs into exception. 即使任务遇到异常,我希望主线程继续。

You can handle exceptions either way, inside or outside. 您可以在内部或外部处理异常。 It's a matter of preference and usually depends on the use-case. 这是一个偏好问题,通常取决于用例。

Note that one thing you aren't doing is keeping a reference to your continuation. 请注意,您没有做的一件事是保留对延续的引用。 You're using Task.Wait on the original task which propogates the exception regardless of the fact that you have a continuation which handles it. 你在原始任务上使用Task.Wait来传播异常,而不管你有一个处理它的延续。

One thing that bothers me is that you're using Task.Wait which synchronously waits instead of await which asynchronously waits . 困扰我的一件事是你正在使用Task.Wait ,它同步等待而不是await 异步等待 Thats the reason for the AggregationException . 这就是AggregationException的原因。 More so, you shouldn't block on asynchronous operations, as that will lead you down a rabit hole you probably don't want to go, with all sorts of synchronization context problems. 更重要的是,你不应该阻止异步操作,因为这会导致你陷入一个你可能不想去的rabit漏洞,伴随着各种同步上下文问题。

What i would personally do, is use await inside of ContinueWith , because its the less verbose option. 我个人会做的是在ContinueWith使用await ,因为它的冗长选项。 Also, i'd use Task.Run over Task.Factory.StartNew : 另外,我在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.
}

The original question happens to run its taskAction on a separate thread. 最初的问题恰好是在一个单独的线程上运行taskAction That might not always be the case. 情况可能并非总是如此。

The answer by i3arnon solves the problem well. i3arnon的答案很好地解决了这个问题。 What if we do not want to use a separate thread? 如果我们不想使用单独的线程怎么办? If we want to simply start a task, running it synchronously until we hit IO or a delay, and then continue about our own business. 如果我们想简单地启动任务,同步运行它,直到我们达到IO或延迟,然后继续我们自己的业务。 Only at the very end, we will wait for the task to complete. 只有在最后,我们才会等待任务完成。

How do we await the task with rethrowing its exception? 我们如何通过重新抛出异常等待任务? Instead of wrapping it in Task.Run() , we will use an empty continuation that will always succeed when the task finishes for any reason. 我们将使用一个空的延续,而不是将其包装在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

It looks like you are on the right track. 看起来你走在正确的轨道上。 I have ran your code and get the same results. 我运行了你的代码,得到了相同的结果。 My suggestion is to use the try/catch directly from inside the action. 我的建议是直接从动作中使用try / catch。 This will make your code clearer and allow you to log things like which thread it came from, which may be different in the continuation path. 这将使您的代码更清晰,并允许您记录它来自哪个线程的内容,这在连续路径中可能不同。

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;

[Edit] Removed outer exception handler. [编辑]删除了外部异常处理程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM