【问题标题】:Exception not thrown from nested async/await嵌套的异步/等待未引发异常
【发布时间】:2019-08-21 06:36:01
【问题描述】:

我有一个 .NET Core 控制台应用程序。在嵌套的 async/await 中发生的异常永远不会被抛出:

    static async Task Main(string[] args)
    {
        try
        {
            var f = new TaskFactory(TaskScheduler.Current);

            await f.StartNew(async () =>
            {
                var x = 0;
                if (x == 0)
                    throw new Exception("we have a problem");

                await Task.Delay(1);
            });
        }
        catch(Exception)
        {
            // never reaches here
        }
    }

如果我删除了内部的async,并放弃对await Task.Delay(1) 的调用,就会捕获到异常。

【问题讨论】:

  • 为什么需要TaskFactory?
  • 异常是在另一个线程中而不是在主线程中引发的,这就是它没有出现的原因
  • 在大多数情况下,您应该使用静态方法Task.Run 而不是TaskFactory.StartNew。例如,它会在异步委托的情况下自动解包内部任务,TaskFactory.StartNew 不会。
  • @IbraHimM.Nada 不应该等待在同一个 SynchronizationContext 中运行的任务从而传播异常?即便如此,为什么当 OP 删除 await Task.Delay(1); 时会引发异常?
  • 这不是await函数的一部分吗?第一个 await 不应该得到结果,因此会遇到任何异常吗?

标签: c# exception async-await console-application


【解决方案1】:

这是一个经典的陷阱。 TaskFactory 期望 Func<T> 并返回 Task<T>。在您的情况下,TTask,因此您最终会得到Task<Task>,您需要等待内部和外部任务。为此使用Unwrap

await f.StartNew(async () =>
{
    var x = 0;
    if (x == 0)
        throw new Exception("we have a problem");

    await Task.Delay(1);
}).Unwrap();

【讨论】:

  • 值得一提的是,Task.StartNew 是已弃用的函数,现在非常少使用,因为它创建的 Tasks 不能很好地与 async await 配合使用并且不理解异步代表。您可能想查看 Stephen Cleary 的文章 StartNew is dangerous
  • @Fabjan StartNew 并没有被弃用,它仍然是使用自定义 TaskScheduler 或自定义标志(例如 LongRunning)启动任务的方式。但是,是的,Task.Run 是最常见情况的首选
  • 老实说,最近两年你用过多少次这种方法?
  • @Fabjan 我是一个非常糟糕的例子,因为我定期处理自定义任务调度程序,所以实际上经常:D 也就是说,即使没有自定义任务调度程序,使用 TaskCreationOptions.LongRunning 应该是一个常见的用例
【解决方案2】:

您的错误来自 StartNew。

当您等待 StartNew 时,您希望里面的工作完成。但是,您给它的工作是创建一个任务。您的 StartNew 具有以下返回类型 Task<Task>。 因为它是您的代码在抛出异常之前结束。

你可以通过两种不同的方式来解决它:

1) 在第一次等待完成后,您等待 start new 的结果

    static async Task Main(string[] args)
    {
        try
        {
            var f = new TaskFactory(TaskScheduler.Current);

            await await f.StartNew(async () =>
            {
                var x = 0;
                if (x == 0)
                    throw new Exception("we have a problem");

                await Task.Delay(1);
            });
        }
        catch(Exception)
        {
            Console.WriteLine("Exception received");
            // never reaches here
        }
        Console.WriteLine("Done");
    }

2) 你停止使用 StartNew 并使用基本任务模式(我推荐)

    static async Task Main(string[] args)
    {
        try
        {
            await Task.Run(async () =>
            {
                var x = 0;
                if (x == 0)
                    throw new Exception("we have a problem");

                await Task.Delay(1);
            });
        }
        catch(Exception)
        {
            Console.WriteLine("Exception received");
            // never reaches here
        }
        Console.WriteLine("Done");
    }

删除 Task.Delay(1) 解决您的问题的原因是因为您将 StartNew 的签名更改为 Task 而不是 Task<Task>,这使您的单个 await 高效

【讨论】:

  • Task.Run 如果 OP 想要使用不同的任务调度程序,则不会这样做
  • 是的,我在他指定之前写了这个。反正我也用他自己的方式提供了解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-04
  • 2018-06-25
  • 2020-10-17
  • 2012-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多