Zé Carlos
Zé Carlos

Reputation: 3807

Calling AutoResetEvent.WaitOne after checking a control variable

Imagine I have a class whose methods are accessed by multiple threads. Imagine that class contains as a private field an integer "i" and some methods increment/decrement that value.

Finally imagine one of my methods has a requirement of blocking (using an AutoResetEvent) every time i == 5

I would write:

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

but what if between the moment I check the value of "i" and the moment I call WaitOne another thread have changed "i"?

I can not wrap the code with a lock block because it would stay blocked forever in case myAutoResetEvent is not signalized.

Any solution for this? Thanks

Upvotes: 1

Views: 466

Answers (4)

loopedcode
loopedcode

Reputation: 4893

You can use double check lock strategy. Although you mention that you don't want to use lock, because it will blocked forever, here using double check, you ensure that lock gets executed only once when it's 5, rest of the time it will not block.

When i == 5, one thread will go into WaitOne passing double check zone, rest of the thread will get blocked by the lock and won't be able to go into WaitOne.

When you signal Set(), the waiting thread will be released out of WaitOne and then it will release the locked block and proceed. One of the other threads that was waiting for the lock, will now move into double check, and now if i is no longer ==5, will not enter WaitOne.

Other threads will follow same pattern, if at any point i == 5 again, one of them will go into WaitOne again.

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

Upvotes: 0

Old Fox
Old Fox

Reputation: 8725

The behavior you've specified is similar to CountdownEvent, however CountdownEvent doesn't allows the counter to be negative(so CountdownEvent which was initialized to 5 won't solve the problem...)

Joe Albahari has implemented a CountdownEvent template which can solve your problem with a few changes:

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

Since there is a correlation between i and the wait request (and you should not violate the SRP...), it better be managed as one object...

BTW, if you don't want to this class/merge it into your class, you can achieve the behavior using 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();

The event won't block unless _i is equals to 5...

Upvotes: 10

Ivan Stoev
Ivan Stoev

Reputation: 205629

The best solution I see for your case is to replace the AutoResetEvent with a custom Monitor based synchronization/signaling construct utilizing the Wait and Pulse / PulseAll methods like this:

Class members:

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

Increment / Decrement methods:

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

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

Sample waiting (the question):

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

Note that the above works due to the unique Wait method behavior described in the documentation:

Releases the lock on an object and blocks the current thread until it reacquires the lock.

and then in the Remarks section:

The thread that currently owns the lock on the specified object invokes this method in order to release the object so that another thread can access it. The caller is blocked while waiting to reacquire the lock. This method is called when the caller needs to wait for a state change that will occur as a result of another thread's operations.

Upvotes: 0

DOMZE
DOMZE

Reputation: 1369

If I understand correctly, your i should be volatile, so that every time a thread accesses it, it gets the latest available value.

Who, however, updates i? I recommend looking into Interlocked.Increment/Decrement methods so that you can be assured that i is incremented in a thread safe manner.

Upvotes: 0

Related Questions