gpt4 book ai didi

c# - ManualResetEventSlim : Calling . Set() 后跟 .Reset() 不会释放 *任何* 等待线程

转载 作者:可可西里 更新时间:2023-11-01 08:10:21 25 4
gpt4 key购买 nike

ManualResetEventSlim:调用 .Set() 后立即调用 .Reset() 不会释放任何等待线程

(注意:ManualResetEvent 也会发生这种情况,而不仅仅是 ManualResetEventSlim。)

我在发布和 Debug模式下都尝试了下面的代码。我在四核处理器上运行的 Windows 7 64 位上使用 .Net 4 作为 32 位版本运行它。我从 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;
}
}

注意:这个问题提出了类似的问题,但似乎没有答案(除了确认是的,这可能会发生)。

Issue with ManualResetEvent not releasing all waiting threads consistently


[编辑]

正如 Ian Griffiths 在下面指出的那样,答案是所使用的底层 Windows API 并非旨在支持这一点。

不幸的是the Microsoft documentation for ManualResetEventSlim.Set()错误地指出它

Sets the state of the event to signaled, which allows one or more threads waiting on the event to proceed.

显然“一个或多个”应该是“零个或多个”。

最佳答案

重置 ManualResetEvent 与调用 Monitor.Pulse 不同 - 它不保证会释放任何特定数量的线程。相反,文档(针对底层 Win32 同步原语)非常清楚,您无法知道会发生什么:

Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object, can be released while the object's state is signaled

这里的关键词是“任何数字”,其中包括零。

Win32 确实提供了 PulseEvent,但正如它所说的那样“此功能不可靠,不应使用。” http://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx 文档中的评论提供一些关于为什么不能用事件对象可靠地实现脉冲式语义的见解。 (基本上,内核有时会将正在等待事件的线程暂时从其等待列表中取出,因此线程总是有可能错过事件的“脉冲”。无论您使用 PulseEvent 都是如此或者您尝试通过设置和重置事件来自己完成。)

ManualResetEvent 的预期语义是它充当一个门。门在您设置时打开,在您重置时关闭。如果您打开一扇门,然后在任何人有机会通过大门之前迅速将其关闭,那么如果每个人仍然站在大门的错误一侧,您就不会感到惊讶。只有那些在你开着门的时候足够警觉的人才能通过。这就是它的工作方式,所以这就是为什么你看到你所看到的。

特别是 Set 的语义非常不是“打开门,并确保所有等待的线程都通过门”。 (如果是这个意思,那么内核应该如何处理多对象等待并不明显。)所以这不是一个“问题”,因为事件并不意味着按照你的方式使用尝试使用它,所以它运行正常。但从某种意义上说,这是一个问题,您将无法使用它来获得您正在寻找的效果。 (这是一个有用的原语,它只是对你想要做的事情没有用。我倾向于将 ManualResetEvent 专门用于最初关闭的门,并且只打开一次,永远不会关闭再次。)

因此您可能需要考虑其他一些同步原语。

关于c# - ManualResetEventSlim : Calling . Set() 后跟 .Reset() 不会释放 *任何* 等待线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15067369/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com