检查控制变量后调用AutoResetEvent.WaitOne

时间:2016-05-26 16:18:52

标签: c# multithreading concurrency thread-safety

想象一下,我有一个类,其方法可由多个线程访问。想象一下,该类包含一个整数“i”作为私有字段,并且某些方法增加/减少该值。

最后想象一下,每当我= = 5时,我的一个方法都需要阻塞(使用AutoResetEvent)

我会写:

if(i == 5)
  myAutoResetEvent.WaitOne()

但是,如果在我检查“i”的值和我调用WaitOne的那一刻之间另一个线程改变了“i”,那该怎么办?

我无法使用lock块包装代码,因为如果未发出myAutoResetEvent信号,它将永远保持阻塞状态。

任何解决方案? 感谢

4 个答案:

答案 0 :(得分:9)

您指定的行为类似于CountdownEvent,但CountdownEvent不允许该计数器为负数(因此CountdownEvent已初始化为5韩元&t; t解决问题...)

Joe Albahari实施了CountdownEvent template,可以通过一些更改来解决您的问题:

public class EqualsWaitHandle
{
    private readonly object _locker = new object();

    private readonly int _lockValue;
    public int Value { get; private set; }

    public EqualsWaitHandle(int lockValue = 0, int initialCount = 0)
    {
        Value = initialCount;
        _lockValue = lockValue;
    }

    public void Signal() { AddCount(-1); }

    public void AddCount(int amount)
    {
        lock (_locker)
        {
            Value += amount;
            if (Value != _lockValue)
                Monitor.PulseAll(_locker);
        }
    }

    public void Wait()
    {
        lock (_locker)
            while (Value  == _lockValue)
                Monitor.Wait(_locker);
    }
}

由于i与等待请求之间存在关联(并且您不应违反SRP ...),因此最好将其作为一个对象进行管理...

顺便说一句,如果您不想要这个课程/将其合并到您的课程中,您可以使用ManualResetEventSlim来实现此行为:

public void AddToVal(int num)
{
    lock (_syncObj)
    {
        _i += num;
        if (_i == 5)
        {
            _event.Reset();
            return;
        }

        _event.Set();
    }
}

// and in the waitable thread:
_event.wait();

除非_i等于5 ...

,否则该事件不会被阻止

答案 1 :(得分:0)

如果我理解正确,你的i应该是易变的,这样每次线程访问它时,它都会获得最新的可用值。

然而,谁更新了我?我建议查看Interlocked.Increment / Decrement方法,以便确保我以线程安全的方式递增。

答案 2 :(得分:0)

您可以使用double check lock strategy。虽然你提到你不想使用锁,因为它将永远被阻止,这里使用双重检查,你确保锁只在它5时执行一次,其余的时间它不会阻塞。

i == 5时,一个线程将进入WaitOne传递双重检查区域,该线程的其余部分将被锁定阻止,并且无法进入WaitOne。

当您发出Set()信号时,等待线程将从WaitOne中释放,然后它将释放锁定的块并继续。等待锁定的其他线程之一现在将进入双重检查,现在如果我不再== 5,则不会进入WaitOne。

其他线程将遵循相同的模式,如果再次i == 5,其中一个将再次进入WaitOne。

private static lock locker = new object();
public MyMethod()
{
  if (i==5)
  {
     lock(locker)
     {
        if (i==5) // is it really == or should it be >=
        { 
          resetEvent.WaitOne();
        }
     }
  }

  //i was not 5, do whatever other work that needs to be done.
  //at some point you would want to decrement i and signal
  //either here or some other method that is finishing the task

  //you can use double check here on a separate lock variable
  //As you haven't described who increments/decrements i, its not clear
  //where/how you are managing i increment/decrement.
  Interlocked.Decrement(ref i);
  if (i < 5)
  {
      resetEvent.Set();
  }
}

答案 3 :(得分:0)

我看到的最佳解决方案是使用MonitorWait / {{3将AutoResetEvent替换为基于自定义Pulse的同步/信令构造像这样的方法:

班级成员:

private object syncLock = new object();
private int i = 0;

增量/减量方法:

private void Increment()
{
    lock (syncLock)
    {
        i++;
        Monitor.PulseAll(syncLock);
    }
}

private void Decrement()
{
    lock (syncLock)
    {
        i--;
        Monitor.PulseAll(syncLock);
    }
}

示例等待(问题):

lock (syncLock)
{
    while (i == 5)
        Monitor.Wait(syncLock);
}

请注意,由于文档中描述的唯一Wait方法行为,上述工作原理如下:

  

释放对象的锁定并阻止当前线程,直到它重新获取锁定。

然后在备注部分:

  

当前拥有指定对象上的锁的线程调用此方法以释放该对象,以便另一个线程可以访问它。等待重新获取锁定时,调用者被阻止。当调用者需要等待由于另一个线程的操作而发生的状态更改时,将调用此方法。