【问题标题】:Why isn't an Exception Caught when Thrown Out of Task.WhenAll?为什么抛出 Task.WhenAll 时没有捕获异常?
【发布时间】:2020-11-04 12:24:08
【问题描述】:

我遇到的问题是异常(标记如下)在抛出到围绕Task.WhenAll 的catch 语句时没有被捕获。我不确定为什么这个异常会膨胀到调用堆栈。

我阅读了以下article,其中声明了一个包装等待的任务的 try/catch。WhenAll 将捕获抛出的第一个异常,但对我来说似乎并非如此......

我在下面有以下代码 sn-p(您可以复制并粘贴它并点击“运行”)以重现(.netcoreapp2.1)。

using System;
using System.Threading.Tasks;
using System.Threading;


namespace TestProgram
{
    class Program
    {
        public static void Main(string[] args)
        {
            MainAsync(args).GetAwaiter().GetResult();
        }

        private static async Task MainAsync(string[] args)
        {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            var thread = new Thread(() => CancelToken(tokenSource));
            thread.IsBackground = true;
            thread.Start();
            var ourTask = SpinUntilCancelled(token);
            try
            {
                await Task.WhenAll(ourTask);
            }
            catch (Exception ex) // NOT CATCHING AND PRINTING!
            { Console.WriteLine($"{ex}"); }
        }

        private static void CancelToken(CancellationTokenSource obj)
        {
            Thread.Sleep(5000);
            obj.Cancel();
            obj.Dispose();
            return;
        }

        private static Task SpinUntilCancelled(CancellationToken cancellation)
        {
            int i = 0;
            while (true)
            {
                try
                {
                    if (cancellation.IsCancellationRequested)
                        throw new ApplicationException("Token cancelled");
                    Thread.Sleep(1000);
                    Console.WriteLine($"Text {++i}");
                }
                catch
                {
                    Console.WriteLine("Cancelled!");
                    cancellation.ThrowIfCancellationRequested();

                }
            }
        }
    }
}

如你所见,我的断点没有被命中。

【问题讨论】:

  • 您的示例中没有异步代码,并且代码从不到达.WhenAlltry/cathc 块内...你错过了await Task.Delay()某处?总体问题看起来只是错字......
  • @AlexeiLevenkov 当它到达 SpinUntilCancelled 并在单独的线程调用 TokenSource 上的 Cancel 之前打印 Text1、Text2、Text3... 时,这怎么可能
  • 不友好或不友好​​的评论:@TeeZadAwk 您是否尝试单步执行代码?你责备try { await Task.WhenAll(ourTask); } ... 代码在它甚至没有被调用... 时不起作用
  • @AlexeiLevenkov:你说得对,对不起......该方法不需要 return 语句,因为它永远无法完成,因为 while (true)。编译器甚至不会抱怨它。
  • @AlexeiLevenkov 你是对的!

标签: c# exception async-await try-catch task


【解决方案1】:

由于while (true) 和缺少breakreturn 语句,SpinUntilCancelled 方法永远不会产生值,因此它的返回类型无关紧要。为了让它产生一个实际的Task,你以后可以await,你应该给它一个适当的异步实现,像这样:

private static async Task SpinUntilCancelled(CancellationToken cancellationToken)
{
    int i = 0;
    while (true)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await Task.Delay(1000, cancellationToken);
        Console.WriteLine($"Text {++i}");
    }
}

附带说明,如果您想在取消CancellationTokenSource 时获得即时通知,您可以像这样通过Register 回调其Token

var tokenSource = new CancellationTokenSource();
tokenSource.Token.Register(() => Console.WriteLine("Token canceled"));

【讨论】:

  • 如果我在最后添加 return Task.CompletedTask 怎么办(在 while 循环之后?我不明白为什么我当前的实现不产生任务。你的实现也不返回任务。
  • @TeeZadAwk 在 while loop 之后添加 return Task.CompletedTask 不会有任何区别,因为循环无法到达该语句。因此,该方法的正确返回类型应该是void,而不是Task。您的方法当前具有异步协定,但没有异步实现。它对调用者说:“嘿!打电话给我,我会给你一个代表异步操作最终完成的任务”。但它并没有兑现这个承诺。它永远阻止调用者,从不返回任何内容。
  • @TeeZadAwk 我的建议是用async/await 实现的。 await 是一个隐式退出点。当内部到达不完整的Task 的第一个await 时,该方法向调用者返回不完整的Task
  • 知道了。如果我不想让它成为一个异步/等待实现,可以从SpinUntilCancelled 返回一个 Task.Run(() => while 循环代码) 吗?
  • @TeeZadAwk 是的,如果您将循环包含在 Task.Run 中,那么功能上就可以了。不过,使用 async/await 机制效率更高,因为您将避免阻塞线程。减少 1 个线程 = 为您的应用程序减少 1 MB 的内存占用。此外,如果您将此代码移植到 WinForms 或 WPF 应用程序,则 async/await 版本循环内的代码将在 UI 线程中运行,因此您将能够与 UI 控件进行交互,而无需求助于调度程序调用。发生这种情况是因为 async/await 机制在每个 await 之后自动恢复同步上下文。
猜你喜欢
  • 2014-08-16
  • 2011-12-12
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 2016-02-17
  • 2021-07-08
  • 2011-11-28
  • 2022-01-06
相关资源
最近更新 更多