.NET 4.6.2中Task调用方法时List参数偶发重置问题求助
问题描述
使用.NET 4.6.2框架编写私有方法doSomething,接收List<myClass>与ConcurrentBag<resClass>参数,用于计算并将结果添加至ConcurrentBag。调用时通过foreach循环创建List<myClass>实例,使用Task.Run异步执行doSomething,但偶发出现传入的list参数中myClass实例的属性全为null的情况。
相关代码
私有方法代码
private void doSomething(List<myClass> list, ConcurrentBag<resClass> cb) { // 对list中的项执行计算逻辑 cb.Add(res); }
调用代码
ConcurrentBag<resClass> cb = new ConcurrentBag<resClass>(); List<Task> bagAddTasks = new List<Task>(); foreach(var item in myList) { List<myClass> myClassList = new List<myClass>() { item }; bagAddTasks.Add(Task.Run(() => doSomething(myClassList, cb))); } // 消费ConcurrentBag中的项 Task.WaitAll(bagAddTasks.ToArray()); List<Task> bagConsumeTasks = new List<Task>(); int itemsInBag = 0; while (!cb.IsEmpty) { bagConsumeTasks.Add(Task.Run(() => { resClass x; if (cb.TryTake(out x)) { resClassList.Add(x); Interlocked.Increment(ref itemsInBag); } })); } Task.WaitAll(bagConsumeTasks.ToArray());
问题排查与解决办法
核心原因
- 引用类型的并发修改:
myClass是引用类型,foreach循环中传递给myClassList的是item的引用。如果在异步任务执行期间,有其他线程修改了myList中对应item的属性,就会导致doSomething中访问到的属性变为null。 - 闭包捕获的潜在问题:虽然.NET 4.5+优化了
foreach循环的变量捕获逻辑,但极端情况下仍可能出现委托延迟执行时捕获到后续迭代的item引用(概率较低,但偶发触发)。
解决办法
1. 避免并发修改原集合中的项
如果myList会被多个线程并发访问或修改,需对myList的读写操作加锁,确保异步任务执行期间原item的属性不会被篡改:
// 对myList的访问加锁 lock(myList) { foreach(var item in myList) { List<myClass> myClassList = new List<myClass>() { item }; bagAddTasks.Add(Task.Run(() => doSomething(myClassList, cb))); } }
2. 显式捕获当前迭代的局部变量
在循环内创建局部变量存储当前item,确保每个异步任务捕获的是独立的变量引用,避免闭包捕获的潜在问题:
foreach(var item in myList) { var currentItem = item; // 显式声明局部变量 List<myClass> myClassList = new List<myClass>() { currentItem }; bagAddTasks.Add(Task.Run(() => doSomething(myClassList, cb))); }
3. 深拷贝myClass实例
如果无法避免原item被并发修改,可在循环内创建item的深拷贝,传递拷贝后的实例给doSomething,彻底隔离原实例的修改影响:
// 实现myClass的深拷贝方法 private myClass CloneMyClass(myClass source) { // 根据myClass的属性实现深拷贝,示例: return new myClass { Prop1 = source.Prop1, Prop2 = source.Prop2, // 引用类型属性需递归拷贝 }; } // 循环内使用拷贝后的实例 foreach(var item in myList) { var clonedItem = CloneMyClass(item); List<myClass> myClassList = new List<myClass>() { clonedItem }; bagAddTasks.Add(Task.Run(() => doSomething(myClassList, cb))); }
4. 修复消费逻辑的线程安全问题
额外注意:消费ConcurrentBag时,resClassList是普通List,多线程调用Add会引发线程安全问题,需改为线程安全集合或加锁:
// 改用线程安全集合 ConcurrentBag<resClass> resClassList = new ConcurrentBag<resClass>(); // 或加锁添加 if (cb.TryTake(out x)) { lock(resClassList) { resClassList.Add(x); } Interlocked.Increment(ref itemsInBag); }
内容的提问来源于stack exchange,提问作者Hallaghan
相关产品推荐
相关产品推荐

