繁体   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