.net 锁定手动重置事件时出现死锁

.net 锁定手动重置事件时出现死锁,.net,deadlock,manualresetevent,.net,Deadlock,Manualresetevent,锁定manualResetEvent实例时遇到死锁。我想不出怎么解决它。我将感谢任何帮助 我在一个类中有两个由不同线程执行的方法: private ManualResetEvent _event = new ManualResetEvent (true); private void process(){ ... lock(_event){ _event.WaitOne(); ... } } internal void Stop(){ _event.Reset(

锁定manualResetEvent实例时遇到死锁。我想不出怎么解决它。我将感谢任何帮助

我在一个类中有两个由不同线程执行的方法:

private ManualResetEvent _event = new ManualResetEvent (true);

private void process(){
  ...
  lock(_event){
    _event.WaitOne();
    ...
  }
}

internal void Stop(){
  _event.Reset();
  lock(_event){
    ...
  }
}
第一个线程触发了锁,并在_event.WaitOne()中被阻止

socond线程执行了行_event.Reset();并在尝试执行锁定(\u事件)时被阻止

我认为当线程1在WaitOne上被阻塞时,锁应该被释放。我想我错了。我不知道该怎么修。 b、 t.w-我添加了锁,因为锁块中的代码应该在两个线程中同步


再次感谢并为这篇冗长的帖子感到抱歉。

我猜您可能对Monitor.Wait(对象)和ManualResetEvent.WaitOne()感到困惑

监视。等待(对象)释放锁并等待,直到它获得锁。ManualResetEvent.WaitOne()阻塞当前线程,直到事件句柄收到信号

我还建议不要同时将ManualResetEvent对象用作锁。即使编译器不会生成错误,这也可能会像您现在可能遇到的那样造成混乱


1.你为什么会陷入僵局 首先,简短的回答是:您错过了Set的重置

我已经复制了您的代码(将大括号更改为我喜欢的样式),我将在注释中解释此问题:

private ManualResetEvent _event = new ManualResetEvent (true);

private void process()
{
  //...
    lock(_event)
    {
        _event.WaitOne(); //Thread A is here waiting _event to be set
        //...
    }
}

internal void Stop()
{
    _event.Reset(); //But thread B just did reset _event
    lock(_event) //And know thread B is here waiting... nobody is going to set _event
    {
        //...
    }
}
清楚了这一部分,让我们继续讨论解决方案


2.解决僵局 由于我们将使用
.Set()
交换
.Reset()
,因此我们还必须将
手动重置事件的默认状态从
true
更改为
false

因此,要解决死锁,请按如下[tested]编辑代码:

private ManualResetEvent _event = new ManualResetEvent (false);

private void process()
{
  //...
    lock(_event)
    {
        _event.WaitOne(); //Thread A will be here waiting _event to be set
        //...
    }
}

internal void Stop()
{
    _event.Set(); //And thread B will set it, so thread a can continue
    lock(_event) //And when thread a releases the lock on _event thread b can enter
    {
        //...
    }
}
private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();

private void process()
{
    //...
    _readWrite.EnterReadLock();
    _event.WaitOne();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitReadLock();
    }
}

internal void Stop()
{
    //there are three relevant thread positions at the process method:
    //a) before _readWrite.EnterReadLock();
    //b) before _event.WaitOne();
    //c) after _readWrite.EnterReadLock();

    _event.Set(); //Threads at position b start to advance
    Thread.Sleep(1); //We want this thread to preempt now!
    _event.Reset(); //And here we stop them
    //Threads at positions a and b wait where they are
    //We wait for any threads at position c
    _readWrite.EnterWriteLock();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitWriteLock();
        //Now the threads in position a continues...
        // but are halted at position b
        //Any thread in position b will wait until Stop is called again
    }
}
上面的代码不仅强制只有一个线程可以同时进入锁,而且强制进入
进程
的线程将等待,直到有一个线程调用
停止


3.但是你有一个比赛条件。。。修好它。 工作没有完成,因为上面的代码患有种族疾病。要理解为什么,想象一下在多个线程调用
进程
的情况下会发生什么。只有一个线程将进入锁,并等待调用
Stop
并设置_事件,然后可以继续。现在,考虑如果调用停止的线程在调用<代码>事件> SETE()/代码>之后被抢先发生了什么,则在<代码>事件中的等待线程继续。现在,您无法判断等待进入
进程
锁定的另一个线程是否将进入,或者在
停止
中抢占的线程是否将继续并以该方法进入锁定。这是一个比赛条件,我不认为你想要那个特别的

也就是说,我为您提供了一个更好的解决方案(经过测试):

private ManualResetEvent _event = new ManualResetEvent (false);

private void process()
{
  //...
    lock(_event)
    {
        _event.WaitOne(); //Thread A will be here waiting _event to be set
        //...
    }
}

internal void Stop()
{
    _event.Set(); //And thread B will set it, so thread a can continue
    lock(_event) //And when thread a releases the lock on _event thread b can enter
    {
        //...
    }
}
private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();

private void process()
{
    //...
    _readWrite.EnterReadLock();
    _event.WaitOne();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitReadLock();
    }
}

internal void Stop()
{
    //there are three relevant thread positions at the process method:
    //a) before _readWrite.EnterReadLock();
    //b) before _event.WaitOne();
    //c) after _readWrite.EnterReadLock();

    _event.Set(); //Threads at position b start to advance
    Thread.Sleep(1); //We want this thread to preempt now!
    _event.Reset(); //And here we stop them
    //Threads at positions a and b wait where they are
    //We wait for any threads at position c
    _readWrite.EnterWriteLock();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitWriteLock();
        //Now the threads in position a continues...
        // but are halted at position b
        //Any thread in position b will wait until Stop is called again
    }
}
阅读代码中的注释以了解其工作原理。简单地说,它需要一个读写锁来允许多个线程进入方法
进程
,但只允许一个线程进入
停止
。尽管已经做了额外的工作,以确保调用方法
进程
的线程将等待,直到有线程调用方法
停止


4.现在你遇到了再入问题。。。修好它。 上面的解决方案更好。。。这并不意味着完美。怎么了?嗯,如果您递归地调用Stop,或者同时从两个不同的线程调用Stop,它将无法正常工作,因为第二个调用可能会在第一个调用执行时使线程进程提前。。。我想你不想这样。显然,使用读写锁足以防止多个线程调用方法
Stop
,但事实并非如此

为了解决这个问题,我们需要确保Stop一次只执行一次。您可以使用lock执行此操作:

private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
//I'm going to use _syncroot, you can use any object...
// as long as you don't lock on it somewhere else
private object _syncroot = new object();

private void process()
{
    //...
    _readWrite.EnterReadLock();
    _event.WaitOne();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitReadLock();
    }
}

internal void Stop()
{
    lock(_syncroot)
    {
        //there are three relevant thread positions at the process method:
        //a) before _readWrite.EnterReadLock();
        //b) before _event.WaitOne();
        //c) after _readWrite.EnterReadLock();

        _event.Set(); //Threads at position b start to advance
        Thread.Sleep(1); //We want this thread to preempt now!
        _event.Reset(); //And here we stop them
        //Threads at positions a and b wait where they are
        //We wait for any threads at position c
        _readWrite.EnterWriteLock();
        try
        {
            //...
        }
        finally
        {
            _readWrite.ExitWriteLock();
            //Now the threads in position a continues...
            // but are halted at position b
            //Any thread in position b will wait until Stop is called again
        }
    }
}
为什么我们需要读写锁您可能会问-如果我们使用lock来确保只有一个线程进入方法
Stop

因为读写锁还允许方法
Stop
处的线程停止调用方法
process
的新线程,同时允许已经存在的线程执行并等待它们完成

为什么我们需要
ManualResetEvent
?-您可能会问-如果我们已经有读写锁来控制方法
进程中线程的执行

因为在调用方法
Stop
之前,读写锁无法阻止方法
进程
中的代码执行

所以,你知道我们需要这些。。。还是我们

好吧,这取决于你有什么样的行为,所以如果我解决了一个和你不一样的问题,我会在下面提供一些替代解决方案


5.具有替代行为的替代解决方案 锁是很容易理解,但它是有点太多了我的口味。。。特别是,如果不需要确保每个对Stop的并发调用都有机会允许线程在方法
进程中执行

如果是这种情况,则可以按如下方式重写代码:

private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
private int _stopGuard;

private void process()
{
    //...
    _readWrite.EnterReadLock();
    _event.WaitOne();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitReadLock();
    }
}

internal void Stop()
{
    if(Interlocked.CompareExchange(_stopGuard, 1, 0) == 0)
    {
        //there are three relevant thread positions at the process method:
        //a) before _readWrite.EnterReadLock();
        //b) before _event.WaitOne();
        //c) after _readWrite.EnterReadLock();

        _event.Set(); //Threads at position b start to advance
        Thread.Sleep(1); //We want this thread to preempt now!
        _event.Reset(); //And here we stop them
        //Threads at positions a and b wait where they are
        //We wait for any threads at position c
        _readWrite.EnterWriteLock();
        try
        {
            //...
        }
        finally
        {
            _readWrite.ExitWriteLock();
            //Now the threads in position a continues...
            // but are halted at position b
            //Any thread in position b will wait until Stop is called again
        }
    }
}
还没有正确的行为?好的,我们再看一个


6.具有替代行为的替代解决方案。。。再一次 这次我们将看到如何允许多线程进入方法
进程
,甚至在调用方法
停止
之前

private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
private int _stopGuard;

private void process()
{
    //...
    _readWrite.EnterReadLock();
    try
    {
        //...
    }
    finally
    {
        _readWrite.ExitReadLock();
    }
}

internal void Stop()
{
    if(Interlocked.CompareExchange(_stopGuard, 1, 0) == 0)
    {
        //there are two relevant thread positions at the process method:
        //a) before _readWrite.EnterReadLock();
        //b) after _readWrite.EnterReadLock();

        //We wait for any threads at position b
        _readWrite.EnterWriteLock();
        try
        {
            //...
        }
        finally
        {
            _readWrite.ExitWriteLock();
            //Now the threads in position a continues...
            // and they will continue until halted when Stop is called again
        }
    }
}
不是你想要的吗

好吧,我放弃。。。让我们回到基本点


7.还有你已经知道的 …为了完整性,如果您只需要确保两个方法的访问是同步的,并且您可以允许进程中的方法在任何时候运行,那么您只需使用锁就可以做到这一点。。。你已经知道了