Task.Run和Task.Factory.StartNew之间的异常处理不同

本文关键字:Task 异常处理 StartNew Run Factory 之间 | 更新日期: 2023-09-27 17:58:03

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

但是,当我使用Task.Factory.StartNew时,没有捕捉到异常。然而,当我使用Task.Run时,它的工作正如我所期望的那样,我认为它只是Task.Factory.StartNew上的包装器(例如,根据MSDN的这篇文章)。

这里提供了一个工作示例,不同之处在于使用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.Run和Task.Factory.StartNew之间的异常处理不同

您的问题是StartNew不能像Task.Run那样与async委托一起工作。StartNew的返回类型为Task<Task>(可转换为Task)。"外部"Task表示方法的开始,"内部"Task表示方法的完成(包括任何异常)。

要获取内部Task,可以使用Unwrap。或者,对于async代码,您可以使用Task.Run而不是StartNewLongRunning只是一个优化提示,实际上是可选的。Stephen Toub有一篇很好的博客文章,介绍了StartNewRun之间的区别,以及为什么Run(通常)更适合async代码。

从下面的@usr注释更新:LongRunning仅适用于async方法的开头(直到第一个未完成的操作为awaited为止)。因此,在这种情况下使用Task.Run几乎肯定会更好。

我会把我的一些评论变成一个答案,因为它们被证明是有用的:

CCD_ 32等同于在实践中强制创建一个新线程。并且您的异步方法可能很长一段时间都不在该线程上(它在第一个等待点被取消)。在这种情况下,你不希望LongRun。

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

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

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

当您第一次打开任务时,这应该是可能的:

await RunTaskAsync().Unwrap();

或者:

await await RunTaskAsync();