如何在Visual Studio 2019中逐行调试C# async、Task或多线程代码
C# 异步多线程代码单线程调试方案
Lock 方案可行性结论
Lock(obj)不可行。Lock仅能实现多线程互斥访问临界区,无法阻止async/await状态机的线程调度:async方法在await后会拆分为多段状态机执行逻辑,后续逻辑依然可能被线程池调度到其他线程运行,即便加锁也只会出现不同线程排队拿锁执行的情况,调试时依然会出现线程跳转,甚至会引发异步上下文死锁问题,不建议使用。
低改动调试方案
方案1:自定义单线程同步上下文(少量代码改动,调试期全局单线程)
仅需新增一个同步上下文实现,并在程序入口注册即可,所有代码可通过编译指令包裹,完全不影响Release版本逻辑:
- 新增自定义同步上下文类
#if DEBUG public class SingleThreadSynchronizationContext : SynchronizationContext { private readonly Queue<(SendOrPostCallback Callback, object State)> _taskQueue = new(); private readonly Thread _mainThread = Thread.CurrentThread; public override void Send(SendOrPostCallback d, object state) { if (Thread.CurrentThread == _mainThread) { d(state); return; } lock (_taskQueue) { _taskQueue.Enqueue((d, state)); } } public override void Post(SendOrPostCallback d, object state) { lock (_taskQueue) { _taskQueue.Enqueue((d, state)); } } public void RunPendingTasks() { while (true) { (SendOrPostCallback Callback, object State) currentTask; lock (_taskQueue) { if (_taskQueue.Count == 0) break; currentTask = _taskQueue.Dequeue(); } currentTask.Callback(currentTask.State); } } } #endif
- 在程序入口(控制台Main、WPF/WinForm启动入口)注册上下文
#if DEBUG var singleThreadCtx = new SingleThreadSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(singleThreadCtx); // 控制台应用需要额外加主循环处理队列 while (true) { singleThreadCtx.RunPendingTasks(); Thread.Sleep(10); } #endif
该方案实现后,所有await的后续回调都会被调度到初始主线程执行,全程无线程切换,可正常逐行调试async/Task代码。
方案2:调试器冻结无关线程(零代码改动)
不需要修改任何业务代码,直接通过调试器操作即可锁定单线程调试:
- 触发断点后,依次打开顶部菜单「调试」->「窗口」->「线程」
- 右键当前处于中断状态的线程,选择「标记」
- 选中所有其他未标记的线程,右键选择「冻结」
后续调试过程中仅当前标记的线程会运行,不会出现其他线程代码跳转的情况,调试完成后在线程窗口全选所有线程点「解冻」即可恢复正常运行。
内容的提问来源于stack exchange,提问作者Pramod Lawate
相关产品推荐
相关产品推荐

