ManualResetEventSlim:调用.Set()后紧跟.Reset()不会';t释放*any*个等待线程
本文关键字:释放 线程 any 等待 不会 Set 调用 ManualResetEventSlim Reset | 更新日期: 2023-09-27 18:00:51
ManualResetEventSlim:调用.Set((后紧跟.Reset((不会释放任何等待线程
(注意:ManualResetEvent
也会发生这种情况,而不仅仅是ManualResetEventSlim
。(
我在发布和调试模式下都尝试了下面的代码。我将它作为一个32位版本运行,在四核处理器上运行的Windows7 64位上使用.Net 4。我从Visual Studio 2012编译了它(因此安装了.Net 4.5(。
当我在我的系统上运行它时,输出是:
Waiting for 20 threads to start
Thread 1 started.
Thread 2 started.
Thread 3 started.
Thread 4 started.
Thread 0 started.
Thread 7 started.
Thread 6 started.
Thread 5 started.
Thread 8 started.
Thread 9 started.
Thread 10 started.
Thread 11 started.
Thread 12 started.
Thread 13 started.
Thread 14 started.
Thread 15 started.
Thread 16 started.
Thread 17 started.
Thread 18 started.
Thread 19 started.
Threads all started. Setting signal now.
0/20 threads received the signal.
因此,设置然后立即重置事件并没有释放任何线程。如果取消对Thread.Sleep((的注释,那么它们都将被释放。
这似乎有些出乎意料。
有人能解释吗?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
public static class Program
{
private static void Main(string[] args)
{
_startCounter = new CountdownEvent(NUM_THREADS); // Used to count #started threads.
for (int i = 0; i < NUM_THREADS; ++i)
{
int id = i;
Task.Factory.StartNew(() => test(id));
}
Console.WriteLine("Waiting for " + NUM_THREADS + " threads to start");
_startCounter.Wait(); // Wait for all the threads to have called _startCounter.Signal()
Thread.Sleep(100); // Just a little extra delay. Not really needed.
Console.WriteLine("Threads all started. Setting signal now.");
_signal.Set();
// Thread.Sleep(50); // With no sleep at all, NO threads receive the signal.
_signal.Reset();
Thread.Sleep(1000);
Console.WriteLine("'n{0}/{1} threads received the signal.'n'n", _signalledCount, NUM_THREADS);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
private static void test(int id)
{
Console.WriteLine("Thread " + id + " started.");
_startCounter.Signal();
_signal.Wait();
Interlocked.Increment(ref _signalledCount);
Console.WriteLine("Task " + id + " received the signal.");
}
private const int NUM_THREADS = 20;
private static readonly ManualResetEventSlim _signal = new ManualResetEventSlim();
private static CountdownEvent _startCounter;
private static int _signalledCount;
}
}
注意:这个问题也提出了类似的问题,但似乎没有答案(除了确认是的,这是可能发生的(。
ManualResetEvent未一致释放所有等待线程的问题
[编辑]
正如Ian Griffiths在下面指出的那样,答案是所使用的底层Windows API并不是为了支持这一点而设计的。
遗憾的是,ManualResetEventSlim.Set((的Microsoft文档错误地指出它是
将事件的状态设置为已发出信号,这允许一个或多个等待事件继续的线程。
显然,"一个或多个"应该是"零个或更多"。
重置ManualResetEvent
与调用Monitor.Pulse
不同——它不能保证它会释放任何特定数量的线程。相反,(底层Win32同步原语的(文档非常清楚,您不知道会发生什么:
任何数量的等待线程,或随后开始对指定事件对象执行等待操作的线程,都可以在对象的状态被通知时释放
这里的关键词是"任何数字",其中包括零。
Win32确实提供了PulseEvent
,但正如它所说"此函数不可靠,不应使用。"http://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85(.aspx提供了一些关于为什么脉冲式语义不能用事件对象可靠实现的见解。(基本上,内核有时会暂时将等待事件的线程从其等待列表中删除,因此线程总是有可能错过事件上的"脉冲"。无论您使用PulseEvent
还是通过设置和重置事件来尝试自己执行,都是如此。(
ManualResetEvent
的意图语义是它充当一个门。当你设置它时,门是打开的,当你重置它时,它是关闭的。如果你打开一扇门,然后在任何人有机会通过它之前迅速关闭它,如果每个人都还在门的错误一侧,你不应该感到惊讶。只有那些在你打开大门时足够警觉地通过大门的人才能通过。这就是它的工作方式,所以这就是为什么你看到了你所看到的。
特别是Set
的语义非常而不是"打开门,并确保所有等待的线程都通过门"。(如果这意味着这一点,那么内核应该如何处理多对象等待还不明显。(因此,这不是一个"问题",因为事件的使用方式与您尝试使用它的方式不同,所以它运行正常。但从某种意义上说,这是一个问题,你无法使用它来获得你想要的效果。(这是一个有用的原语,但对你想要做的事情没有用处。我倾向于将ManualResetEvent
专门用于最初关闭的门,这些门只打开一次,再也不会关闭。(
因此,您可能需要考虑其他一些同步原语。