异步方法中Debug.WriteLine输出异常值的问题排查
异步循环中变量捕获问题的解决方案
你遇到的是典型的闭包捕获循环变量问题,和lock无关——lock解决的是多线程竞争共享资源的场景,你的问题根源是任务委托捕获的是变量的引用,而非当前迭代的数值。
问题本质
你在循环外声明了int x = 99;,循环内仅做x = i;赋值。但Task的委托是延迟执行的:当任务真正启动执行ExtractDataSetCode(x)时,循环已经跑完,x已经被更新为最后一个值5。所有任务捕获的都是同一个x变量的引用,自然都会输出5,看不到0-4的中间值。
修复方案
把变量声明移到循环内部,让每次循环都创建一个独立的变量,这样每个任务捕获的都是当前循环迭代的专属变量,不会被后续循环覆盖:
private async void btnStart_Click(object sender, EventArgs e) { var tskDataSetCodes = new List<Task>(); Stopwatch s = new Stopwatch(); s.Start(); // 将计时启动移到循环外,避免多次重置计时器 for (int i = 0; i < 6; i++) { int x = i; // 关键:循环内声明变量,每个任务捕获独立的x实例 Task kickoffTasks = Task.Run(() => ExtractDataSetCode(x)); // 用Task.Run替代new Task+Start,更简洁规范 System.Diagnostics.Debug.WriteLine($"DataSet : {x} Task: {kickoffTasks.Id}"); tskDataSetCodes.Add(kickoffTasks); } await Task.WhenAll(tskDataSetCodes); s.Stop(); var tasktime = s.ElapsedMilliseconds; System.Diagnostics.Debug.WriteLine($"Application Completed {tasktime}ms"); } static async Task ExtractDataSetCode(int a) // 把async void改为async Task,方便异常处理和任务等待 { System.Diagnostics.Debug.WriteLine($"ExtractDataSetCode: {a} on thread: {Thread.CurrentThread.ManagedThreadId}"); var tasks = new List<Task>(); for (var count = 1; count <= 10; ++count) { int currentA = a; // 保险起见,循环内声明变量避免闭包问题(值类型参数虽会复制,但写法更严谨) Task queryTasks = Task.Run(() => queryProcess(currentA)); tasks.Add(queryTasks); } await Task.WhenAll(tasks); } static void queryProcess(int z) { System.Diagnostics.Debug.WriteLine($"Starting queryProcess for dataset: {z} on thread: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(10000); // System.Diagnostics.Debug.WriteLine($"Ending queryProcess for dataset: {z} on thread: {Thread.CurrentThread.ManagedThreadId}"); }
额外优化说明
- 用
Task.Run替代new Task(...) + Start(),这是创建后台异步任务的推荐简洁写法。 - 将
async void的ExtractDataSetCode改为async Task,async void仅适用于事件处理程序,其他场景用async Task能更好地处理异常和等待任务完成。 - 把
Stopwatch.Start()移到循环外,否则每次循环调用Start会重置计时器,导致最终计时结果不准确。
内容的提问来源于stack exchange,提问作者adveach
相关产品推荐
相关产品推荐

