C#中为何Test吞掉Task异常死循环而Test_1能正常抛出异常?
C# Task异常吞噬问题原理解释
核心规则:Task异常的传播前提
C#中Task执行过程中抛出的未处理异常,会被自动捕获并封装在AggregateException对象中,存储在当前Task实例里,只有代码主动观察该Task的异常时,异常才会被抛出到调用上下文。常见的异常观察行为包括:
- 调用Task的
Result属性 - 调用Task的
Wait()方法 - 使用
await关键字等待Task完成 - 主动访问Task的
Exception属性
如果没有任何上述观察操作,该异常会被判定为未观察的Task异常,默认不会主动抛到启动Task的线程中,更不会向上传递到外层调用栈。
两种测试方法的差异说明
1. Test()方法异常被吞噬的原因
Test内部调用的SomeMethod<T>逻辑为:启动Task后立刻返回默认值,全程没有等待Task完成、也没有任何观察Task异常的操作。SomeMethod返回后,Test方法直接进入死循环,主线程会一直卡在死循环逻辑中,既不会触发Task的异常观察逻辑,也不会让已经结束的Task实例被GC回收(如果是.NET Framework版本,未观察Task异常本会在GC回收Finalize阶段抛出,但死循环会导致Task实例一直被引用,不会触发回收),因此Task的异常会一直被存储在Task实例中,永远不会被抛到外层调用栈,看起来就像被"吞掉"了。
2. Test_1()方法正常抛出异常的原因
Test_1内部调用的SomeMethod_1<T>中,启动Task后调用了testTask.Result,这个操作会同步阻塞当前线程,等待Task执行完成,同时属于明确的Task异常观察行为。
当Task执行5秒后抛出异常,Result属性会立刻将封装的异常抛到当前调用线程,进而向上传递到外层的try-catch块中,符合预期被捕获。
内容的提问来源于stack exchange,提问作者K. Carter Sr.
相关产品推荐
相关产品推荐

