C#异步代码中不使用Task.Delay如何强制触发上下文切换
问题根因
你遇到的死循环本质是async方法的同步执行段阻塞了调用线程:调用DoSomething(cts.Token)时,方法会在当前线程同步执行直到遇到第一个未完成的awaitable。await Task.CompletedTask的目标任务已经完成,不会触发挂起,代码会同步继续执行循环,永远不会返回调用方,自然也跑不到后面的cts.Cancel()代码。你加Task.Delay(1)能解决问题,本质是Delay返回的未完成任务触发了挂起,让出了线程控制权。
最优实现方案
不用Task.Run也不用额外延时的最优方案是使用await Task.Yield(),它的设计目的就是强制让出当前执行上下文,让调度器优先执行队列中其他已排队的任务,没有额外的计时开销,也不会切换到非主线程执行:
var tasks = new List<Task>(); var cts = new CancellationTokenSource(); tasks.Add(DoSomething(cts.Token)); cts.Cancel(); Task.WaitAll(tasks.ToArray()); Console.WriteLine("Done"); async Task DoSomething(CancellationToken ct) { // 进入逻辑前先让出一次,保证调用方有机会执行Cancel逻辑 await Task.Yield(); while (!ct.IsCancellationRequested) { // 这里编写你的业务逻辑 await Task.CompletedTask; // 若循环单次执行耗时较长,可在循环内定期加await Task.Yield()保证取消及时响应 } }
如果你的业务场景允许循环内让出,也可以把Yield放到循环内部,效果完全一致。该方案适配所有带同步上下文的主线程场景(UI线程、单线程单元测试调度上下文等),完全满足你要在主线程执行逻辑的需求。
内容的提问来源于stack exchange,提问作者Red Riding Hood
相关产品推荐
相关产品推荐

