多线程方法中更新List内计数器的最佳线程安全实现方案
线程安全更新List中值类型计数器的解决方案
问题根源
你当前使用的是值类型元组,从List中取出的temp是原元组的副本。修改副本再赋值回List的过程中,其他线程可能已经修改了List中的原元组,导致更新丢失。Interlocked.Exchange之所以无效,是因为它操作的是副本的numProcessed字段,而非List中存储的原实例。
正确的线程安全实现
使用**循环+Interlocked.CompareExchange**实现原子性替换,确保更新操作不会被并发请求覆盖:
// 循环重试直到CAS操作成功 while (true) { // 获取当前List中的元组副本 var currentTuple = lstDataSetCdTasks[dataSetNum]; // 计算新的计数器值 int newCount = currentTuple.numProcessed + 1; // 创建更新后的新元组(C# 8.0+ with 表达式简化字段复制) var updatedTuple = currentTuple with { numProcessed = newCount }; // CAS原子替换:仅当List中当前元素仍为currentTuple时,才替换为updatedTuple var originalTuple = Interlocked.CompareExchange(ref lstDataSetCdTasks[dataSetNum], updatedTuple, currentTuple); // 替换成功则退出循环 if (originalTuple.Equals(currentTuple)) { break; } // 替换失败(其他线程已修改元素),重新尝试 } // 读取更新后的值 var dataSetItem = lstDataSetCdTasks[dataSetNum]; currRow.Cells["Extract"].Value = $"{dataSetItem.numProcessed / dataSetItem.modVal} | % ({dataSetItem.queryTasks.Count} threads)";
关键说明
Interlocked.CompareExchange针对List的索引元素执行原子替换,避免了并发场景下的更新覆盖问题。- 循环重试是为了处理“其他线程在读取和替换之间修改元素”的情况,确保最终能完成更新。
- 若使用C# 8.0以下版本,可手动复制元组的所有字段来创建新实例,替代
with表达式。
长远优化建议
虽然需要在现有框架下解决,但将值类型元组改为引用类型类会大幅简化线程安全实现。例如:
public class DataSetTaskContext { public string DataSetCode { get; set; } public int DataSetNum { get; set; } public int RowCount { get; set; } public double ModVal { get; set; } public int NumProcessed { get; set; } public List<Task> QueryTasks { get; set; } }
此时更新计数器只需一行原子操作:
Interlocked.Increment(ref lstDataSetCdTasks[dataSetNum].NumProcessed);
内容的提问来源于stack exchange,提问作者adveach
相关产品推荐
相关产品推荐

