【问题标题】:Different exception handling between Task.Run and Task.Factory.StartNewTask.Run 和 Task.Factory.StartNew 之间的不同异常处理
【发布时间】:2013-01-21 16:34:21
【问题描述】:

我在使用Task.Factory.StartNew 并尝试捕获抛出的exception 时遇到问题。在我的应用程序中,我有一个长时间运行的任务,我想将其封装在 Task.Factory.StartNew(.., TaskCreationOptions.LongRunning);

但是,当我使用Task.Factory.StartNew 时,没有捕获到异常。然而,当我使用Task.Run 时,它可以正常工作,我认为这只是Task.Factory.StartNew 的一个包装器(例如根据this MSDN article)。

此处提供了一个工作示例,不同之处在于使用Task.Run 时会将异常写入控制台,而使用Factory.StartNew 时则不会。

我的问题是:
如果我有一个可能引发异常的LongRunning 任务,我应该如何在调用代码中处理它们?

private static void Main(string[] args)
{
    Task<bool> t = RunLongTask();
    t.Wait();
    Console.WriteLine(t.Result);
    Console.ReadKey();
}

private async static Task<bool> RunLongTask()
{
    try
    {
        await RunTaskAsync();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
        return false;
    }
    Console.WriteLine("success");
    return true;
}

private static Task RunTaskAsync()
{
    //return Task.Run(async () =>
    //    {
    //        throw new Exception("my exception");
    //    });
    return Task.Factory.StartNew(
        async () =>
    {
        throw new Exception("my exception");
    });

}

【问题讨论】:

  • 如果你执行 Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default) 会发生什么 - 你得到相同的结果吗?
  • @MatthewWatson 是的,不幸的是同样的结果
  • 另外,请参见此处:blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx(接近末尾的一位 cmets 提出了类似的问题)
  • @MatthewWatson 我看不出是哪条评论。来自nick 的评论似乎只处理await Task.Run(() =&gt; ..await Task.Run(async () =&gt; .. 之间的区别
  • 嗯,是的,我假设异步的事情与它有关。也许不是!

标签: c# exception task-parallel-library taskfactory


【解决方案1】:

您的问题是 StartNew 不像 Task.Runasync 代表一样工作。 StartNew 的返回类型为Task&lt;Task&gt;(可转换为Task)。 “外部”Task 表示方法的开始,“内部”Task 表示方法的完成(包括任何异常)。

要到达内部Task,您可以使用Unwrap。或者,您可以只使用 Task.Run 而不是 StartNew 作为 async 代码。 LongRunning 只是一个优化提示,实际上是可选的。 Stephen Toub has a good blog post on the difference between StartNew and Run and why Run is (usually) better for async code.

从下面的@usr 评论更新: LongRunning 仅适用于async 方法的开头(直到第一个不完整的操作为awaited)。所以在这种情况下使用Task.Run 几乎肯定会更好。

【讨论】:

  • 在我的工作中,我们鼓励使用这些优化提示。如果我不使用Factory.StartNew 的返回值而不是将其转换为async void 会更好吗(甚至可能)?
  • 为减少混淆,我建议将其设为单独的方法。另请阅读 Stephen Toub 的博文;如果您使用较旧的 StartNew 和较新的 async 代码,您可能还想调用 Unwrap
  • 我的回答不正确;这不是导致问题的 lambda 转换。这是Task 的包装。我已将我的答案更新为……嗯……。 :)
  • LongRunning 等同于在实践中强制创建一个新线程。而且您的异步方法可能很长时间不在该线程上(它在第一个等待点被取消)。在这种情况下,您不想要 LongRunning。
  • @Default 编译器通常无法以任何主要方式分析您的代码。此外,编译器对 TPL 一无所知。 TPL 是一个库。这个库总是会启动一个新线程。如果您的任务几乎总是会在几秒钟内消耗 100% 的 CPU,或者很可能会阻塞几秒钟,则指定 LongRunning。
【解决方案2】:

你第一次Unwrap这个任务应该是可以的:

await RunTaskAsync().Unwrap();

或者:

await await RunTaskAsync();

【讨论】:

    【解决方案3】:

    我会将我的一些 cmets 拉到一个答案中,因为事实证明它们很有帮助:

    LongRunning 与在实践中强制创建新线程相同。而且您的异步方法可能很长时间不在该线程上(它在第一个等待点被取消)。在这种情况下,您不需要 LongRunning。

    异步方法运行多长时间并不重要。线程在第一次等待时被销毁(对未完成的任务进行操作)。

    编译器可以以任何方式使用此提示吗?编译器通常无法以任何主要方式分析您的代码。此外,编译器对 TPL 一无所知。 TPL 是一个库。这个库总是会启动一个新线程。指定LongRunning,如果您的任务几乎总是会在多秒内消耗 100% CPU 或以非常高的概率阻塞多秒。

    我的猜测是你不希望 LongRunning 在这里,因为如果你阻塞,你为什么首先使用异步? async 不是阻塞而是脱离线程。

    【讨论】:

    • 您对LongRunning 在第一个等待点被占用有任何参考吗?我想了解更多有关它的信息
    • @Default await 将回调排队等待操作完成时执行。完成后,它总是从函数返回。 TPL 看到返回并结束任务(和线程)(请记住,这里有两个任务 - 请参阅 Stephen Cleary 的回答。一个(外部)任务在第一个等待点完成并返回内部任务,而内部任务在结束)。请参阅 Eric Lippert 的名为“C# 5 中的异步”的博客系列。该语言不保证“在第一个等待点被占用”。这源于巧合的其他行为。
    猜你喜欢
    • 2016-09-24
    • 2014-05-16
    • 1970-01-01
    • 1970-01-01
    • 2016-09-13
    • 2015-11-11
    • 2018-05-07
    • 2019-02-16
    • 2016-11-20
    相关资源
    最近更新 更多