You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程方法中更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 14:22:29