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)实现:
- 创建
CancellationTokenSource; - 将令牌传入
Track方法,在循环中检查令牌是否已取消; - 使用
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

