C#中async/await延迟时长为何影响执行行为?是否存在竞态条件?
问题:修改
Task.Delay时长为何影响异步任务的并行执行? 以下代码原本预期两个Service实例同时启动:
List<Service> services = [ new Service(), new Service() ]; foreach (Service service in services) { Task task = service.StartAsync(); } Console.ReadLine(); class Service { public async Task StartAsync() { await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("HH:mm:ss")}\tStarted"); await Task.Delay(TimeSpan.FromSeconds(1)); //Simulate some synchronous work. Thread.Sleep(10_000); await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("HH:mm:ss")}\tFinished"); } }
原始输出(延迟1秒):
20:32:25 Started 20:32:25 Started 20:32:35 Finished 20:32:35 Finished
但将延迟从TimeSpan.FromSeconds(1)改为TimeSpan.FromMicroseconds(1)后,输出变为:
20:40:10 Started 20:40:20 Finished 20:40:20 Started 20:40:30 Finished
请问为何修改延迟时长会影响执行行为?是否存在竞态条件?
解答
这不是竞态条件,核心原因是**Task.Delay的时长会影响异步方法的调度行为**,结合控制台程序的线程调度规则导致了差异:
异步方法的执行逻辑差异:
- 调用
StartAsync后,先执行await Console.Out.WriteLineAsync,这一步异步完成后方法挂起,主线程回到循环启动第二个Service的StartAsync,所以两个Started会同时输出。 - 执行
await Task.Delay时:- 当延迟为1秒时,
Task.Delay会创建真正的异步延迟操作,方法挂起,线程回到线程池。此时两个任务都处于等待状态,延迟结束后线程池会分配两个独立线程执行后续的Thread.Sleep(10000),两个同步阻塞任务并行进行,最终同时完成。 - 当延迟为1微秒时,
Task.Delay会直接返回一个已完成的Task(系统因延迟过短,内部优化为同步完成)。此时await不会挂起方法,直接执行后续的Thread.Sleep(10000),这会阻塞当前主线程。
- 当延迟为1秒时,
- 调用
控制台主线程的阻塞影响:
控制台程序的初始任务启动依赖主线程,第一个StartAsync执行到Thread.Sleep时阻塞了主线程,导致循环无法及时启动第二个StartAsync。直到第一个任务的Thread.Sleep结束,主线程才会回到循环启动第二个任务,最终表现为串行执行。
简单总结:短到极致的Task.Delay被优化为同步执行,后续阻塞代码占用了启动任务的主线程,使得第二个任务无法及时启动;正常时长的Task.Delay触发异步挂起,释放主线程启动第二个任务,从而实现并行。
内容的提问来源于stack exchange,提问作者Sandy
相关产品推荐
相关产品推荐

