为何Task会呈现不同状态?异步Lambda代码异常案例解析
Async Lambda 结合 Task 导致的 ContinueWith 提前触发问题
示例代码
var task1 = new Task(async () => { while (true) { Console.WriteLine("Hello from Task1"); await Task.Delay(TimeSpan.FromSeconds(5)); } }); task1.ContinueWith(_ => { Console.WriteLine("Hello from continue 1"); }); var task2 = new Task(() => { while (true) { Console.WriteLine("Hello from Task2"); Task.Delay(TimeSpan.FromSeconds(5)).Wait(); } }); task2.ContinueWith(_ => { Console.WriteLine("Hello from continue 2"); }); task1.Start(); task2.Start(); while (true) { await Task.Delay(TimeSpan.FromSeconds(7)); }
输出差异
- 预期输出:每隔5秒交替输出
Hello from Task1和Hello from Task2,两个ContinueWith的输出永远不会出现。 - 实际输出:程序启动后立即输出
Hello from continue 1,之后每隔5秒正常输出Hello from Task1和Hello from Task2,Hello from continue 2始终不出现。
Task1的状态异常
task1启动后不会持续处于Running状态,而是很快进入RanToCompletion状态,与持续处于Running的task2形成明显区别。
技术解释
1. Async Lambda 与 Task 构造函数的不匹配
Task类的构造函数接受的是Action类型委托,当传入async lambda时,这个lambda会被编译为异步void方法(因为没有接受Func<Task>的Task构造函数重载)。
异步void方法的核心特性是:当执行到第一个未完成的await时,方法会立即返回,不会等待后续代码执行。对于外层的task1来说,它只负责跟踪Action委托的执行完成——既然异步void方法已经返回,task1就会认为自己已经执行完毕,直接进入RanToCompletion状态。
2. ContinueWith 的触发逻辑
ContinueWith是绑定在Task的完成事件上的,一旦task1进入RanToCompletion状态,绑定的回调就会立刻执行,所以你会看到Hello from continue 1在启动后立即输出。
但异步void方法中await之后的代码(循环的后续执行)并不会消失,它会被捕获到异步状态机中,在Task.Delay完成后继续在线程池线程上执行,所以Hello from Task1依然会每隔5秒输出一次。
3. Task2 的正常逻辑对比
task2使用的是同步Action委托,内部通过Task.Delay(...).Wait()阻塞当前线程,整个委托会一直处于循环执行的状态,永远不会完成。因此task2始终处于Running状态,绑定的ContinueWith永远不会被触发。
内容的提问来源于stack exchange,提问作者Roman Matkovskyi
相关产品推荐
相关产品推荐

