Reputation: 3807
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
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
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
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
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