【问题标题】:TaskContinuationOptions OnlyOnCancelled catches unhandled errorsTaskContinuationOptions OnlyOnCancelled 捕获未处理的错误
【发布时间】:2019-03-07 05:38:59
【问题描述】:

我有以下代码来处理我的TaskContinuations。我有点困惑,因为我有低于OnlyOnFaulted 块,如果任务引发未处理的异常,我希望输入该块。

但是,未处理的异常、使用 throw 重新抛出的已处理异常或取消将落在 OnlyOnCanceled 块中。

GetDataAsync(id).ContinueWith((antecedant) =>
{
    // do something when async method completed
    }, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith((antecedant) =>
    {
        var error = antecedant.Exception.Flatten(); //so when is this called if everything is cought by OnCancelled below?
    }, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith((antecedant) =>
    {
        // this is fired if method throws an exception or if CancellationToken cancelled it or if unhandled exception cought
        var error = "Task has been cancelled";
    }, TaskContinuationOptions.OnlyOnCanceled);

我希望重新抛出的错误和取消将出现在 OnlyOnCanceled 块中,而未处理的异常将出现在 OnlyOnFaulted 块中

请注意,我不能只是 await GetDataAsync,因为这是在从 View 的 c-tor 调用的方法中调用的。我在这篇文章中解释了NetworkStream ReadAsync and WriteAsync hang infinitelly when using CancellationTokenSource - Deadlock Caused by Task.Result (or Task.Wait)

更新

我使用的是下面的 Task.Run,​​而不是使用上面的代码。按照 Jon Goldberger 在https://blog.xamarin.com/getting-started-with-async-await/

的建议,我正在用 async 装饰传递给 Task.Run 的 lambda,以提供“一路异步”
Task.Run(async() =>  
{
    try
    {
        IList<MyModel> models = await GetDataAsync(id);
        foreach (var model in models)
        {
            MyModelsObservableCollection.Add(model);
        }
    } catch (OperationCancelledException oce) {}
    } catch (Exception ex) {}

});

这感觉是一个更好的解决方案,因为我可以使用 try...catch 块将代码包装在 Task.Run 中,并且异常处理的行为与我预期的一样。

我绝对打算尝试一下 Stephen Cleary 在https://msdn.microsoft.com/en-us/magazine/dn605875.aspx 提供的建议,因为它有望成为一个更清洁的解决方案。

【问题讨论】:

  • 您应该真正使用await 来添加延续,而不是ContinueWith,基本上在所有情况下,正是因为编写这样的代码要正确地编写代码要困难得多。省心。
  • 这不是不使用await 的理由。鉴于您对问题的描述,您应该绝对使用await 添加这些延续。如果您确实想触发并忘记此操作(这不太可能,那么在构造函数中不太可能有一些事情要做,您不想知道它何时完成或从构造函数返回一个对象时它没有'没有完成一些用于构造它的操作(如果它不是用于构造它的操作,那么你不应该从构造函数中调用它))然后你可以触发并忘记一个异步方法。

标签: c# async-await task continuations


【解决方案1】:

正如我在另一个答案中所说,您应该使用await,并且由于这是 ViewModel 的构造函数,您应该使用synchronously initialize to a "Loading..." state and asynchronously update that ViewModel to a "Display Data" state

直接回答这个问题,问题是ContinueWith返回一个任务代表延续,而不是前件。为了简化您问题中的代码:

GetDataAsync(id)
    .ContinueWith(A(), TaskContinuationOptions.OnlyOnRanToCompletion);
    .ContinueWith(B(), TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(C(), TaskContinuationOptions.OnlyOnCanceled);

如果GetDataAsync(id) 运行完成,将调用A()。如果A() 出现故障,B() 将被调用(注意:如果GetDataAsync(id) 出现故障,则不会)。如果B() 被取消,C() 将被调用(注意:如果GetDataAsync(id) 被取消,则不会)。

您在使用ContinueWith 时还有其他一些问题:它缺少一些标志(例如DenyChildAttach),并且它使用的是当前的TaskScheduler,这可能会导致令人惊讶的行为。 ContinueWith is an advanced, low-level method; use await instead.

【讨论】:

  • 谢谢斯蒂芬。一旦有机会,我会尝试一下,并按照链接文章中的建议尝试您的 NotifyTaskCompletion。几天前我阅读了那篇文章并喜欢该解决方案,但现在,我在上面的更新部分中使用该解决方案,因为它不会给我带来任何问题,并允许我继续在我的项目中添加更多必需的功能(时间紧缩)。出于这个原因,我做了一个笔记来回过头来思考 NotifyTaskCompletion,感谢那篇文章。同时,如果您可以对上面的 UPDATE 部分发表评论,我们将不胜感激。谢谢
  • ... 但要评论您对 ContinueWith 的评论,最初我试图将 TaskScheduler.FromCurrentSynchronizationContext() 传递给我的 ContinueWith 并遇到问题,它在与传入的一起使用时抱怨某些事情任务继续选项。我不记得到底是什么问题,但我确实记得我只有在添加 TaskScheduler 时才会遇到问题:“},TaskContinuationOptions.OnlyOnRanToCompletion,TaskScheduler.FromCurrentSynchronizationContext()”。我还没来得及调查它,但我记得只传递延续效果很好(除了你的观点)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-10
  • 2012-02-21
  • 1970-01-01
相关资源
最近更新 更多