C# 是否有一个.Net类来执行ManualResetEvent.Pulsell()将执行的操作(如果存在)?

C# 是否有一个.Net类来执行ManualResetEvent.Pulsell()将执行的操作(如果存在)?,c#,multithreading,C#,Multithreading,是否有一个.Net类来执行ManualResetEvent.pulsell()将执行的操作(如果存在) 我需要自动释放一组等待同一信号的线程。(对于我的预期用途,我不担心“线程踩踏”。) 不能使用ManualResetEvent执行此操作。例如,如果您这样做: ManualResetEventSlim signal = new ManualResetEventSlim(); // ... signal.Set(); signal.Reset(); 然后,根本不会释放等待信号的线程 如果在Set

是否有一个.Net类来执行
ManualResetEvent.pulsell()
将执行的操作(如果存在)

我需要自动释放一组等待同一信号的线程。(对于我的预期用途,我不担心“线程踩踏”。)

不能使用
ManualResetEvent
执行此操作。例如,如果您这样做:

ManualResetEventSlim signal = new ManualResetEventSlim();
// ...
signal.Set();
signal.Reset();
然后,根本不会释放等待信号的线程

如果在
Set()
Reset()
调用之间放置
Thread.Sleep(5)
,则会释放一些但不是所有等待的线程。将睡眠时间增加到10ms可以释放所有线程。(这是用20个螺纹测试的。)

显然,添加
Thread.Sleep()
来实现这一点是不可接受的

然而,使用
Monitor.pulsell()
很容易做到这一点,为此我编写了一个很小的类。 (我之所以编写一个类来实现这一点,是因为我们发现使用Monitor的逻辑虽然相当简单,但并不明显,不值得拥有这样一个类来简化使用。)

我的问题很简单:在.Net中是否已经有这样一个类

以下是我的“
ManualResetEvent.pulsell()
”等效版本的基本版本供参考:

public sealed class Signaller
{
    public void PulseAll()
    {
        lock (_lock)
        {
            Monitor.PulseAll(_lock);
        }
    }

    public void Wait()
    {
        Wait(Timeout.Infinite);
    }

    public bool Wait(int timeoutMilliseconds)
    {
        lock (_lock)
        {
            return Monitor.Wait(_lock, timeoutMilliseconds);
        }
    }

    private readonly object _lock = new object();
}

下面是一个示例程序,它演示了如果不在Set()和Reset()之间休眠,则不会释放任何等待线程:

使用系统;
使用系统线程;
使用System.Threading.Tasks;
名称空间演示
{
公共静态类程序
{
私有静态void Main(字符串[]args)
{
_startCounter=新的倒计时事件(NUM_线程);
对于(int i=0;itest(id));
}
WriteLine(“等待”+NUM_线程+“线程启动”);
_startCounter.Wait();//等待所有线程都已启动。
睡眠(100);
WriteLine(“线程全部启动。现在设置信号”);
_signal.Set();
//Thread.Sleep(5);//如果根本没有睡眠,则没有线程接收到信号。请尝试注释这一行。
_信号复位();
睡眠(1000);
WriteLine(“\n{0}/{1}个线程收到了信号。\n\n“,\u signaledcount,NUM\u threads);
控制台。WriteLine(“按任意键退出”);
Console.ReadKey();
}
专用静态无效测试(int id)
{
_startCounter.Signal();//用于让主线程知道所有线程何时启动。
_信号。等待();
联锁增量(参考信号计数);
WriteLine(“任务”+id+“接收到信号”);
}
private const int NUM_THREADS=20;
专用静态只读ManualResetEventSlim _信号=新的ManualResetEventSlim();
私有静态倒计时事件_startCounter;
专用静态int_信号计数;
}
}
您可以使用对象。它允许运行未指定数量的任务,然后等待所有其他任务到达该点


如果您不知道哪些代码块中的哪些任务将作为特定的工作单元开始工作,则可以使用它。

版本1
最大清晰度:在每个
pulsell
循环开始时,迫切需要安装一个新的
ManualResetEvent

public class PulseEvent
{
    public PulseEvent()
    {
        mre = new ManualResetEvent(false);
    }

    ManualResetEvent mre;

    public void PulseAll() => Interlocked.Exchange(ref mre, new ManualResetEvent(false)).Set();

    public bool Wait(int ms) => Volatile.Read(ref mre).WaitOne(ms);

    public void Wait() => Wait(Timeout.Infinite);
};
版本2
此版本避免为任何碰巧在没有等待器的情况下完成的
pulsell
周期创建内部事件。每个周期的第一个服务员进入一个乐观的无锁竞赛,创建并自动安装一个共享事件

public class PulseEvent
{
    ManualResetEvent mre;

    public void PulseAll() => Interlocked.Exchange(ref mre, null)?.Set();

    public bool Wait(int ms)
    {
        ManualResetEvent tmp =
           mre ??
           Interlocked.CompareExchange(ref mre, tmp = new ManualResetEvent(false), null) ??
           tmp;
        return tmp.WaitOne(ms);
    }

    public void Wait() => Wait(Timeout.Infinite);
};
版本3
此版本通过分配两个持久的
ManualResetEvent
对象并在它们之间翻转,消除了每个周期的分配。与上述示例相比,这稍微改变了语义,如下所示:

  • 首先,回收相同的两个锁意味着你的
    PulseAll
    周期必须足够长,以允许所有服务员清除前一个锁。否则,当您连续两次快速调用
    pulsell
    时,先前的
    pulsell
    调用假定释放的任何等待线程(但操作系统还没有机会安排)也可能会在新周期中被重新阻塞。我提到这一点主要是出于理论上的考虑,因为这是一个没有实际意义的问题,除非你在亚微秒脉冲周期内阻塞了大量线程。您可以决定此条件是否与您的情况相关。如果是这样,或者您不确定或谨慎,您可以始终使用上面的版本1版本2,它们没有此限制

  • 在这个版本中,对
    pulsell
    的调用被认为基本上是同时的,这意味着除了一个之外,所有的多个“同时”调用方都被合并了。这种行为并非没有先例(参见),可能是可取的,具体取决于应用

请注意,后一点必须被视为合法的设计选择,而不是bug、理论缺陷或并发错误。这是因为脉冲锁在多个同时脉冲的情况下本质上是不明确的:具体地说,没有办法证明没有被指定的单个脉冲发生器释放的任何服务员也必然会被其他合并/省略的脉冲之一释放

用另一种方式说,这种类型的锁并不是为了原子化地序列化
pulsell
调用方而设计的,事实上它确实不可能,因为它总是可能的
public class PulseEvent
{
    ManualResetEvent mre;

    public void PulseAll() => Interlocked.Exchange(ref mre, null)?.Set();

    public bool Wait(int ms)
    {
        ManualResetEvent tmp =
           mre ??
           Interlocked.CompareExchange(ref mre, tmp = new ManualResetEvent(false), null) ??
           tmp;
        return tmp.WaitOne(ms);
    }

    public void Wait() => Wait(Timeout.Infinite);
};
public class PulseEvent
{
    public PulseEvent()
    {
        cur = new ManualResetEvent(false);
        alt = new ManualResetEvent(true);
    }

    ManualResetEvent cur, alt;

    public void PulseAll()
    {
        ManualResetEvent tmp;
        if ((tmp = Interlocked.Exchange(ref alt, null)) != null) // try claiming 'pulser'
        {
            tmp.Reset();                     // prepare for re-use, ending previous cycle
            (tmp = Interlocked.Exchange(ref cur, tmp)).Set();    // atomic swap & pulse
            Volatile.Write(ref alt, tmp);    // release claim; re-allow 'pulser' claims
        }
    }

    public bool Wait(int ms) => cur.WaitOne(ms);  // 'cur' is never null (unlike 'alt')

    public void Wait() => Wait(Timeout.Infinite);
};