它是安全的信号,并立即关闭ManualResetEvent的? [英] Is it safe to signal and immediately close a ManualResetEvent?

查看:139
本文介绍了它是安全的信号,并立即关闭ManualResetEvent的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我觉得我应该知道这个问题的答案,但我要问反正以防万一我正在做一个潜在的灾难性错误。

下面code按预期执行没有错误/异常:

 静态无效的主要(字串[] args)
{
    ManualResetEvent的标志=新的ManualResetEvent(假);
    ThreadPool.QueueUserWorkItem(S =>
    {
        flag.WaitOne();
        Console.WriteLine(工作项目1时执行);
    });
    ThreadPool.QueueUserWorkItem(S =>
    {
        flag.WaitOne();
        Console.WriteLine(工作项目2执行的);
    });
    Thread.sleep代码(1000);
    flag.Set();
    flag.Close();
    Console.WriteLine(完成);
}
 

当然,和通常的多线程code的情况下,一次试车成功并不能证明这其实是线程安全的。该测试也成功,如果我把关闭设置,即使该文件明确指出,试图以后做任何事关闭将导致不确定的行为。

我的问题是,当我调用 ManualResetEvent.Set 的方法,它是保证信号的所有等待线程的控制返回给调用者?换句话说,假设我能保证不会再有进一步呼吁的WaitOne ,它是安全的,在这里关闭句柄,或者是有可能,下某些情况下,这code将prevent有些服务员从得到信号,或者导致的ObjectDisposedException

该文件只是说设置将其置于一个信号状态 - 这似乎并没有做出什么时候服务员会的实际上得到任何索赔的信号,所以我想是肯定的。

解决方案

在信号与 ManualResetEvent.Set 你保证,所有正在等待的线程事件(即阻塞状态的 flag.WaitOne )控制返回给调用者之前会发出信号。

当然,有些时候,你可能会设置标志的情况下,你的线程没有看到它,因为它在做一些工作,它会检查前旗(或nobugs如果您要创建多个线程建议):

  ThreadPool.QueueUserWorkItem(S =>
{
    QueryDB();
    flag.WaitOne();
    Console.WriteLine(工作项目1时执行);
});
 

有争的标志,现在你可以创建未定义的行为,当您关闭它。您的标志是你的线程之间共享资源,你应该创建其中每个线程信号完成后倒计时锁存器。这将消除争你的标志

 公共类CountdownLatch
{
    私人诠释m_remain;
    私人的EventWaitHandle m_event;

    公共CountdownLatch(诠释计数)
    {
        复位(计数);
    }

    公共无效复位(诠释计数)
    {
        如果(计数℃,)
            抛出新ArgumentOutOfRangeException();
        m_remain =计数;
        m_event =新的ManualResetEvent(假);
        如果(m_remain == 0)
        {
            m_event.Set();
        }
    }

    公共无效信号()
    {
        //最后一个线程也发出信号设置事件。
        如果(Interlocked.Decrement(参考m_remain)== 0)
            m_event.Set();
    }

    公共无效等待()
    {
        m_event.WaitOne();
    }
}
 

  1. 在倒计时锁存每个线程的信号。
  2. 您的主线程等待进入倒计时闩锁。
  3. 在主线程倒计时锁存信号后清理。

最后,你睡在最后的时间是不是一种安全的方式来照顾你的问题,而不是你应该设计你的程序,因此它在多线程环境100%安全。

更新:一个生产者/消费者多
<打击>的$ P $这里psumption是,你的制作人知道有多少消费者会被创建,在创建所有的消费者,您重置 CountdownLatch 与消费者的给定数量:

  //在生产者
ManualResetEvent的标志=新的ManualResetEvent(假);
CountdownLatch倒计时=新CountdownLatch(0);
INT numConsumers = 0;
而(hasMoreWork)
{
    消费者消费=新用户(coutndown,旗);
    //创建与每一个消费者一个新的线程
    numConsumers ++;
}
countdown.Reset(numConsumers);
flag.Set();
countdown.Wait(); //您的生产者等待所有的消费者以完成
flag.Close(); //清除
 

I feel like I should know the answer to this, but I'm going to ask anyway just in case I'm making a potentially catastrophic mistake.

The following code executes as expected with no errors/exceptions:

static void Main(string[] args)
{
    ManualResetEvent flag = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(s =>
    {
        flag.WaitOne();
        Console.WriteLine("Work Item 1 Executed");
    });
    ThreadPool.QueueUserWorkItem(s =>
    {
        flag.WaitOne();
        Console.WriteLine("Work Item 2 Executed");
    });
    Thread.Sleep(1000);
    flag.Set();
    flag.Close();
    Console.WriteLine("Finished");
}

Of course, as is usually the case with multi-threaded code, a successful test does not prove that this is actually thread safe. The test also succeeds if I put Close before Set, even though the documentation clearly states that attempting to do anything after a Close will result in undefined behaviour.

My question is, when I invoke the ManualResetEvent.Set method, is it guaranteed to signal all waiting threads before returning control to the caller? In other words, assuming that I'm able to guarantee that there will be no further calls to WaitOne, is it safe to close the handle here, or is it possible that under some circumstances this code would prevent some waiters from getting signaled or result in an ObjectDisposedException?

The documentation only says that Set puts it in a "signaled state" - it doesn't seem to make any claims about when waiters will actually get that signal, so I'd like to be sure.

解决方案

When you signal with a ManualResetEvent.Set you are guaranteed that all the threads that are waiting for that event (i.e. in a blocking state on flag.WaitOne) will be signaled before returning control to the caller.

Of course there are circumstances when you might set the flag and your thread doesn't see it because it's doing some work before it checks the flag (or as nobugs suggested if you're creating multiple threads):

ThreadPool.QueueUserWorkItem(s =>
{
    QueryDB();
    flag.WaitOne();
    Console.WriteLine("Work Item 1 Executed");
});

There is contention on the flag and now you can create undefined behavior when you close it. Your flag is a shared resource between your threads, you should create a countdown latch on which every thread signals upon completion. This will eliminate contention on your flag.

public class CountdownLatch
{
    private int m_remain;
    private EventWaitHandle m_event;

    public CountdownLatch(int count)
    {
        Reset(count);
    }

    public void Reset(int count)
    {
        if (count < 0)
            throw new ArgumentOutOfRangeException();
        m_remain = count;
        m_event = new ManualResetEvent(false);
        if (m_remain == 0)
        {
            m_event.Set();
        }
    }

    public void Signal()
    {
        // The last thread to signal also sets the event.
        if (Interlocked.Decrement(ref m_remain) == 0)
            m_event.Set();
    }

    public void Wait()
    {
        m_event.WaitOne();
    }
}

  1. Each thread signals on the countdown latch.
  2. Your main thread waits on the countdown latch.
  3. The main thread cleans up after the countdown latch signals.

Ultimately the time you sleep at the end is NOT a safe way to take care of your problems, instead you should design your program so it is 100% safe in a multithreaded environment.

UPDATE: Single Producer/Multiple Consumers
The presumption here is that your producer knows how many consumers will be created, After you create all your consumers, you reset the CountdownLatch with the given number of consumers:

// In the Producer
ManualResetEvent flag = new ManualResetEvent(false);
CountdownLatch countdown = new CountdownLatch(0);
int numConsumers = 0;
while(hasMoreWork)
{
    Consumer consumer = new Consumer(coutndown, flag);
    // Create a new thread with each consumer
    numConsumers++;
}
countdown.Reset(numConsumers);
flag.Set();
countdown.Wait();// your producer waits for all of the consumers to finish
flag.Close();// cleanup

这篇关于它是安全的信号,并立即关闭ManualResetEvent的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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