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

C#多线程:主线程安全等待子线程进入WaitOne阻塞的方案

C#轮询线程安全同步方案

核心竞态根因:你之前考虑的双ManualResetEvent方案失效,本质是子线程通知主线程"已到等待点"、子线程实际执行WaitOne()进入阻塞这两个操作没有原子性保证,线程调度可以在两个操作之间任意切换,导致状态判断错位。

下面的方案无死锁、无竞态,不会强制中断子线程当前轮询逻辑,不需要复杂的跨线程信号嵌套:

具体实现步骤

1. 给设备类新增同步辅助成员

不需要新增额外的重置事件,只需要加一个锁对象和状态标记即可:

public class SpeedCounterDevice
{
    // 原有的轮询控制事件
    public ManualResetEvent resetevent = new ManualResetEvent(false);
    // 锁对象:仅用来保护线程状态的读写
    private readonly object _pollStateLock = new object();

    // 轮询子线程状态
    private enum PollThreadStatus
    {
        Working, // 正在执行轮询业务(网络收发、探活等)
        Waiting  // 已阻塞在WaitOne,不会触碰套接字
    }
    private PollThreadStatus _pollStatus = PollThreadStatus.Working;

    // 其他原有成员(套接字、业务方法等)
}

2. 修改子线程轮询逻辑

把状态更新、通知主线程的逻辑放在锁块内,WaitOne()必须放在锁外执行,避免死锁:

private static void doPollThread(object thisObject)
{
    SpeedCounterDevice sc = (SpeedCounterDevice)thisObject;
    while (true)
    {
        // 进入等待前,原子更新状态并通知主线程
        lock (sc._pollStateLock)
        {
            sc._pollStatus = PollThreadStatus.Waiting;
            Monitor.PulseAll(sc._pollStateLock);
        }

        // 阻塞等待轮询信号,此时不持有任何锁
        sc.resetevent.WaitOne();

        // 被唤醒后先更新状态为工作中,再执行业务
        lock (sc._pollStateLock)
        {
            sc._pollStatus = PollThreadStatus.Working;
        }

        // 原有轮询业务逻辑:网络探活、数据读取等
        // do some work ....
        Thread.Sleep(400);
    }
}

3. 主线程安全等待逻辑

主线程需要断开/重连套接字前,按下面的逻辑同步即可,100%保证子线程已经进入等待状态,不会碰套接字:

// 第一步:先重置轮询信号,阻止子线程下一轮继续执行
sc.resetevent.Reset();

// 第二步:阻塞等待子线程真正进入等待状态
lock (sc._pollStateLock)
{
    // 必须用while循环判断,规避Monitor虚假唤醒问题
    while (sc._pollStatus != PollThreadStatus.Waiting)
    {
        Monitor.Wait(sc._pollStateLock);
    }
}

// 代码走到这里时,子线程已经完全阻塞在WaitOne()位置
// 可以安全执行套接字断开、重连、参数修改等任何操作,不会有并发冲突
// 操作完成需要恢复轮询时,直接调用 sc.resetevent.Set() 即可

方案说明

  • 无竞态:子线程更新状态、发送通知的操作在锁内原子完成,主线程必须拿到同一把锁才能检查状态,不存在操作间隙。子线程释放锁后第一时间执行WaitOne(),等主线程拿到锁确认状态时,子线程必然已经进入阻塞。
  • 无死锁:所有等待资源的操作(WaitOne()、Monitor.Wait())都不会在持有锁的状态下执行,不会出现锁交叉等待。
  • 不打断当前轮询:只有子线程完整执行完当前轮询的所有业务逻辑、跑完Thread.Sleep(400)之后,才会更新状态通知主线程,完全符合不强制中断当前轮询的要求。
  • 性能开销极低:锁粒度只覆盖状态变量的读写,不会阻塞正常的轮询业务执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:03:54