【问题标题】:ContinueWith TaskContinuationOptions.OnlyOnFaulted does not seem to catch an exception thrown from a started taskContinueWith TaskContinuationOptions.OnlyOnFaulted 似乎没有捕获从已启动任务引发的异常
【发布时间】:2015-03-09 21:38:54
【问题描述】:

我正在尝试使用 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 关联的继续任务被执行。

【问题讨论】:

  • 您使用的是哪个版本的 .NET 框架?
  • @YuvalItzchakov 使用 .NET 4.5。

标签: c# .net exception-handling task-parallel-library async-await


【解决方案1】:

最初的问题恰好在单独的线程上运行其taskAction。情况可能并非总是如此。

The answer by 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

【讨论】:

    【解决方案2】:

    我是正确使用TaskContinutationOptions.OnlyOnFaulted 还是总是更好 处理任务方法本身的异常?我想要主 即使任务遇到异常,线程也会继续。

    您可以在内部或外部以任何方式处理异常。这是一个偏好问题,通常取决于用例。

    请注意,您没有做的一件事是保留对您的延续的引用。您在传播异常的原始任务上使用Task.Wait,而不管您有一个处理它的延续。

    困扰我的一件事是您使用的是Task.Wait,它同步等待,而不是await,它异步等待。这就是AggregationException 的原因。更重要的是,你不应该阻塞异步操作,因为这会导致你陷入一个你可能不想去的兔子洞,以及各种同步上下文问题。

    我个人会做的是在ContinueWith 中使用await,因为它是不那么冗长的选项。另外,我会使用Task.Run over Task.Factory.StartNew:

    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.
    }
    

    【讨论】:

    • 感谢您的回答,尤其是异常传播。我无法投票赞成您的回答(可能是因为我是新来的,没有足够的声誉:()
    • 我需要同步等待。意图是在我走得更远之前有两种方法要完成(并行),所以我猜 await 在我的场景中不起作用。我什至可以为这两种方法使用 Parallel.Invoke。
    • 为什么要同步等待?
    • 比方说,我必须调用返回列表的 methodA() 和返回另一个列表的 methodB()。我将不得不调用另一个带有输入参数的服务方法作为列表,该列表是由 methodA() 和 methodB() 返回的列表的某种联合。在这种情况下,我可以并行调用 methodA() 和 methodB() 但我必须等待它们返回并形成结果列表。这个列表我将发送到服务,并且我已经形成了另一个文档,其中包含服务返回的内容。这是用户交互,用户将等待操作结束。
    • @seesharpconcepts 为什么不直接使用await Task.WhenAll(taskA, taskB);
    【解决方案3】:

    首先,您没有正确使用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
    }
    

    【讨论】:

    • 这是我正在寻找的信息。我无法投票可能是因为我是新来的,没有足够的声誉:(
    • @seesharpconcepts 是的,在你达到 15 分之前你不能投票给答案。更多信息在这里:meta.stackexchange.com/q/1661/246036
    • 使用此代码,如果您等待继续任务并且第一个任务中没有异常,则会引发任务取消异常。
    【解决方案4】:

    看起来您走在正确的轨道上。我已经运行了您的代码并得到了相同的结果。我的建议是直接从动作内部使用 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;
    

    [编辑] 移除了外部异常处理程序。

    【讨论】:

    • 不需要内部 try catch ....要捕获异常,您需要将 try catch 放在调用等待或任务结果的位置...
    • 同意,但我花时间阅读了规范“我想从任务方法中记录该异常。”
    • @PhillipScottGivens 我在想为什么即使在任务方法中的异常情况下 ContiueWith 任务 onfaulted 也没有被执行。我通读了一篇 MSDN 文章,该文章建议使用 ContinueWith 和 onlyonfaulted 来处理来自任务的异常。
    • 当我运行你的代码时,ContinueWith 任务确实被执行了,但它并没有阻止异常进一步冒泡。再次尝试您的代码并在您的 Console.Writeline 上放置一个断点。为了清楚起见,请考虑将其移至另一行。
    • @PhillipScottGivens :正如 I3arnon 在他的回答中提到的,一旦处理了 主要任务的异常, continuetask 将被执行。注意:我没有否决您的回答,感谢您帮助我。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-23
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 2014-10-07
    相关资源
    最近更新 更多