ManualResetEventSlim:调用。设置()紧跟.reset段()不释放任何* *等待的线程 [英] ManualResetEventSlim: Calling .Set() followed immediately by .Reset() doesn't release *any* waiting threads

查看:157
本文介绍了ManualResetEventSlim:调用。设置()紧跟.reset段()不释放任何* *等待的线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ManualResetEventSlim:调用。设置()紧跟.reset段()不释放的任何的等待线程

ManualResetEventSlim: Calling .Set() followed immediately by .Reset() doesn't release any waiting threads

(注意:这也与的ManualResetEvent 发生,不仅与 ManualResetEventSlim

(Note: This also happens with ManualResetEvent, not just with ManualResetEventSlim.)

我试着在发布和调试模式下的代码。
我正在为它使用.NET 4.0的Windows 7 32位版本的四核处理器,运行64-bit。
我从Visual Studio 2012(所以.NET 4.5安装)编译它

I tried the code below in both release and debug mode. I'm running it as a 32-bit build using .Net 4 on Windows 7 64-bit running on a quad core processor. I compiled it from Visual Studio 2012 (so .Net 4.5 is installed).

当我在我的系统上运行它的输出是:

The output when I run it on my system is:

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()方法,然后将它们全部释放。

So setting and then immediately resetting the event did not release a single thread. If you uncomment the Thread.Sleep(), then they are all released.

这似乎有些出人意料。

有没有人有一个解释

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;
    }
}

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

Note: This question poses a similar problem, but it doesn't seem to have an answer (other than confirming that yes, this can happen).

问题与ManualResetEvent的没有释放所有等待的线程始终

由于伊恩·格里菲思低于所指出的,答案是,所用的目的不是底层的Windows API支持这一点。

As Ian Griffiths points out below, the answer is that the underlying Windows API that is used is not designed to support this.

这是不幸的为ManualResetEventSlim.Set(Microsoft文档)指出错误,它

It's unfortunate that the Microsoft documentation for ManualResetEventSlim.Set() states wrongly that it

设定,以通知的事件的状态,它允许一个或多个
线程在等待上进行该事件。

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

显然,一个或多个应为零个或多个。

Clearly "one or more" should be "zero or more".

推荐答案

重置的ManualResetEvent 是不喜欢打电话 Monitor.Pulse - 它不保证它会释放线程的任何特定数目。相反,文档(对于基础Win32同步原语)是很清楚,你无法知道会发生什么:

Resetting a ManualResetEvent is not like calling Monitor.Pulse - it makes no guarantee that it will release any particular number of threads. On the contrary, the documentation (for the underlying Win32 synchronization primitive) is pretty clear that you can't know what will happen:

任何等待线程,或者说,随后开始等待操作指定的事件对象的线程数,而对象的状态信号可以被释放。

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

这里的关键词是任意数量,其中包括零。

The key phrase here is "any number" which includes zero.

Win32的确实提供了一个 PulseEvent ,但因为它说这个函数是不可靠的,不应该被使用。在 http://msdn.microsoft的说明文件中的言论.COM / EN-US /库/窗/台式机/ ms684914(v = vs.85)的.aspx 提供一些洞察为什么脉冲式的语义不能可靠地与事件对象实现的。 (基本上,内核有时需要正在等待暂时关闭它的等待列表中的一个事件线程,因此它总是可能的,一个线程将错过一个事件脉冲。这是真的,你是否使用了 PulseEvent 或试图通过设置和重置的情况下自己做。)

Win32 does provide a PulseEvent but as it says "This function is unreliable and should not be used." The remarks in its documentation at http://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx provide some insight into why pulse-style semantics cannot reliably be achieved with an event object. (Basically, the kernel sometimes takes threads that are waiting for an event off its wait list temporarily, so it's always possible that a thread will miss a 'pulse' on an event. That's true whether you use PulseEvent or you try to do it yourself by setting and resetting the event.)

预期的语义的ManualResetEvent 是,它作为一个门。门是打开的,当你设置它,当你重新设置为关闭。如果你打开门,然后迅速将其关闭之前,任何人都有机会通过大门得到的,如果每个人都仍然在门的错边,你不应该感到惊讶。只有那些谁是足够的警觉通过门,而你持有它打开将打通搞定。这就是它的目的是工作,所以这就是为什么你看到你所看到的。

The intended semantics of ManualResetEvent is that it acts as a gate. The gate is open when you set it, and is closed when you reset it. If you open a gate and then quickly close it before anyone had a chance to get through the gate, you shouldn't be surprised if everyone is still on the wrong side of the gate. Only those who were alert enough to get through the gate while you held it open will get through. That's how it's meant to work, so that's why you're seeing what you see.

在特定的设置的语义非常多的不可以打开大门,并确保所有等待的线程都通过门。 (而且如果它的意思是,这不是明显的是内核应该多对象等待做的。)所以这不是意义上的问题是不是意味着在事件中使用你的方式尝试使用它,所以它的运作正常。但正是在这个意义上,你将不能够使用它来获取你要找的效果的问题。 (这是一个有用的原始的,它只是不适合你正在试图做什么用。我倾向于使用的ManualResetEvent 专为那些最初关闭,并得到完全打开大门一次,永远不会再关闭。)

In particular the semantics of Set are very much not "open gate, and ensure all waiting threads are through the gate". (And if it were to mean that, it's not obvious what the kernel should do with multi-object waits.) So this is not a "problem" in the sense that the event isn't meant to be used the way you're trying to use it, so it's functioning correctly. But it is a problem in the sense that you won't be able to use this to get the effect you're looking for. (It's a useful primitive, it's just not useful for what you're trying to do. I tend to use ManualResetEvent exclusively for gates that are initially closed, and which get opened exactly once, and never get closed again.)

所以你可能需要考虑一些其他的同步原语。

So you probably need to consider some of the other synchronization primitives.

这篇关于ManualResetEventSlim:调用。设置()紧跟.reset段()不释放任何* *等待的线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆