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

C#多线程错误:预期单线程执行的循环为何出现多线程并发?

并发控制逻辑缺陷说明

现有代码的并发控制存在多个层级的问题,其中直接导致多线程同时进入foreach循环的根因如下:

  • finally块触发范围错误,导致占用标志被提前返回的线程误重置
    代码将_isCurrentlyUpdatingValues = false的重置逻辑放在了整个方法的finally块中,只要进入try范围的代码,无论后续是正常执行完所有逻辑,还是在lock块中检测到标志位为true直接return,都会强制执行finally块的代码。这意味着没有拿到执行权、本来应该直接退出的线程,也会错误地把其他正在执行更新逻辑的线程设置的占用标志改成false,直接给后续线程放行。

多线程闯入的典型复现时序(单次foreach执行耗时超过定时器10秒间隔时必现):

  1. 首次触发的线程1进入lock,检测到_isCurrentlyUpdatingValues为false,将其设为true后释放锁,进入foreach执行逻辑
  2. 10秒后第二次触发的线程2进入lock,检测到标志位为true,执行return退出try块,此时会强制执行finally块,把_isCurrentlyUpdatingValues错误重置为false,但线程1仍在foreach中运行
  3. 后续调度的线程3进入lock时,检测到标志位已经被误改为false,就会再次将标志位设为true,释放锁后也进入foreach执行
  4. 上述流程会反复触发,每次有线程检测到占用提前返回,就会错误重置标志位放行新线程,最终出现3个及以上线程同时在foreach内的现象

除了核心根因外,代码还存在两个并发设计缺陷:

  • 锁的保护范围与设计预期不符
    锁仅覆盖了标志位的检查、赋值操作,真正需要互斥执行的foreach临界区完全在锁保护范围外;且finally中重置标志位的操作也没有被锁覆盖,多线程场景下即使没有finally误触发的问题,也可能因为指令重排、CPU缓存可见性问题,导致不同线程读取到的标志位值不一致,出现并发闯入。
  • 标志位修改没有权限控制
    逻辑没有区分“成功获取到执行权的线程”和“检测到占用直接退出的线程”,所有进入方法的线程都可以修改标志位的值,本身就不符合互斥锁的设计原则。

最小修复实现

修复核心是调整try/finally的范围,保证只有成功拿到执行权的线程,才能在执行完临界区逻辑后重置标志位,参考代码如下:

object _objLock = new object();
bool _isCurrentlyUpdatingValues = false;
public void UpdateTestReadValuesTimerCallback(object state)
{
    // 仅做执行权抢占,不持有锁执行长耗时逻辑
    lock (_objLock)
    {
        if (_isCurrentlyUpdatingValues)
        {
            return;
        }
        _isCurrentlyUpdatingValues = true;
    }

    // 只有抢占到执行权的线程才能进入该try块
    try
    {
        foreach (var prop in CommConfigDnp3.Dnp3Properties)
        {
            if (prop.ReadOrWrite == ReadOrWrite.Read)
            {
                if (prop.PropertyType == PropertyType.Bool)
                {
                    bool? value = CommConfigDnp3.ReadBinary(prop.PropertyName);
                    prop.LastValue = value;
                }
                else
                {
                    double? value = CommConfigDnp3.ReadDouble(prop.PropertyName);
                    prop.LastValue = value;
                }
            }
        }
    }
    finally
    {
        // 执行完临界区后,加锁重置标志位
        lock (_objLock)
        {
            _isCurrentlyUpdatingValues = false;
        }
    }
}

内容的提问来源于stack exchange,提问作者Eric Ouellet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:09:25