You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

while(true)循环中异步Exception被吞的问题及解决咨询

问题描述

我在应用中新增的异步代码抛出错误时无法生成日志。应用会启动少量长期运行的async Task,每个任务都有自己的while(true)循环。我在多处设置了错误日志,但发现异常传播到Task内的无限while循环边界时就会停止——最初日志仅设置在循环外部,虽然该Task会停止运行,但错误并未被记录。

以下示例程序复现了该行为:

  • 移除while(true)后,Track()方法仅执行一次,异常会冒泡到RunAsync()方法,应用崩溃(符合预期);
  • 保留循环时,编号2的任务会继续运行,编号1的任务则静默终止,异常完全没被捕获。

我知道未等待的Task可能吞掉异常,但我认为已正确await所有需要等待的内容,若有误请指正。

示例代码

static void Main(string[] args)
{
    Console.WriteLine("Starting up");
    RunAsync().GetAwaiter().GetResult();
    Console.WriteLine("Shutting down");
}

private static async Task RunAsync()
{
    try
    {
        Console.WriteLine("In RunAsync");
        List<int> trackers = new List<int> { 1, 2 };

        await Task.WhenAll(trackers.Select(tracker => Track(tracker)));
    }
    catch (Exception e)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} => Error in RunAsync\n{e.StackTrace}");
        throw;
    }
}

private static async Task Track(int number)
{
    Console.WriteLine($"Starting tracker for {number}");

    while (true)    // 这个循环导致异常无法向上传播
    {
        Console.WriteLine($"In here for number {number}");

        if (number == 1)
        {
            throw new Exception("Oops! Number 1 does not work");
        }

        await Task.Delay(1000);
    }
}

带循环的输出

Starting up
In RunAsync
Starting tracker for 1
In here for number 1
Starting tracker for 2
In here for number 2
In here for number 2
In here for number 2

不带循环的输出

Starting up
In RunAsync
Starting tracker for 1
In here for number 1
Starting tracker for 2
In here for number 2
16:58:34.916 => Error in RunAsync
   at Program.<Track>d__2.MoveNext() in Program.cs:line 48
--- End of stack trace from previous location where exception was thrown ---

请问针对此类循环内可能出现的异常,我应如何进行正确处理?


问题原因

核心问题在于Task.WhenAll会等待所有传入的任务完成,而编号2的任务是无限循环,永远不会结束。当编号1的任务抛出异常后,它会进入Faulted状态,但Task.WhenAll会一直等待编号2的任务,永远不会触发后续的异常抛出,导致RunAsync里的catch块永远不会执行,异常就被“隐藏”了。

解决方案

针对这种长期运行的循环任务,有两种常用的正确处理方式:

1. 在循环内部捕获异常(推荐)

直接在Track方法的while循环内添加try-catch,可以立即捕获循环迭代中出现的异常,记录日志后还能决定是否终止任务或继续运行。

修改后的Track方法示例:

private static async Task Track(int number)
{
    Console.WriteLine($"Starting tracker for {number}");

    while (true)
    {
        try
        {
            Console.WriteLine($"In here for number {number}");

            if (number == 1)
            {
                throw new Exception("Oops! Number 1 does not work");
            }

            await Task.Delay(1000);
        }
        catch (Exception e)
        {
            // 记录异常日志
            Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} => Error in tracker {number}\n{e.StackTrace}");
            // 可选:如果异常不可恢复,退出循环终止任务
            break;
            // 或者如果想继续运行,注释break即可
            // await Task.Delay(5000); // 出错后延迟一段时间再重试
        }
    }
}

2. 为每个任务单独绑定异常处理

如果不想修改Track方法,可以在创建任务时通过ContinueWith为每个任务添加异常处理逻辑,这样即使Task.WhenAll还在等待其他任务,单个任务的异常也能被及时捕获。

修改RunAsync方法的任务创建部分:

private static async Task RunAsync()
{
    Console.WriteLine("In RunAsync");
    List<int> trackers = new List<int> { 1, 2 };

    // 为每个任务绑定异常处理延续
    var continuationTasks = trackers.Select(tracker => 
        Track(tracker).ContinueWith(task => 
        {
            if (task.IsFaulted)
            {
                var e = task.Exception?.InnerException ?? task.Exception;
                Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} => Error in tracker {tracker}\n{e?.StackTrace}");
            }
        }, TaskContinuationOptions.OnlyOnFaulted)
    );

    // 等待主任务完成
    await Task.WhenAll(trackers.Select(Track));
}

补充:任意任务出错时终止所有任务

如果需要在任意任务出错时立即终止所有任务,可以结合Task.WhenAny和取消令牌(CancellationToken)实现:

  1. 创建CancellationTokenSource;
  2. 将令牌传入Track方法,在循环中检查令牌是否已取消;
  3. 使用Task.WhenAny监控所有任务,一旦有任务出错,就取消令牌,让其他任务也终止。

示例代码(简化版):

private static async Task RunAsync()
{
    try
    {
        Console.WriteLine("In RunAsync");
        List<int> trackers = new List<int> { 1, 2 };
        using var cts = new CancellationTokenSource();

        var tasks = trackers.Select(tracker => Track(tracker, cts.Token));
        var completedTask = await Task.WhenAny(tasks);

        // 如果完成的任务是出错状态,取消所有任务并抛出异常
        if (completedTask.IsFaulted)
        {
            cts.Cancel();
            await completedTask; // 抛出异常触发上层catch
        }

        await Task.WhenAll(tasks);
    }
    catch (Exception e)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} => Error in RunAsync\n{e.StackTrace}");
        throw;
    }
}

private static async Task Track(int number, CancellationToken token)
{
    Console.WriteLine($"Starting tracker for {number}");

    while (!token.IsCancellationRequested)
    {
        Console.WriteLine($"In here for number {number}");

        if (number == 1)
        {
            throw new Exception("Oops! Number 1 does not work");
        }

        await Task.Delay(1000, token);
    }
}

内容的提问来源于stack exchange,提问作者Danny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 07:03:17