Introduction
While studying several articles on the C# IDisposable
pattern (e.g. Implementing IDisposable and the Dispose Pattern Properly[^]), I was contemplating on the thread safe access of the Dispose()
method.
If I have the need to program code that runs exactly once in a multi-threaded environment, there are several approaches. Let's look into some alternatives:
Ignorant Approach
The ignorant approach simply does not care about multi-threading - no good.
Ignorant |
---|
bool _called = false;
void Ignorant()
{
if (!_called)
{
_called = true;
}
}
|
Locked Access
These approaches take care of multi-threading issues. But they are both a bit verbose, and one has to not forget setting the lock
properly (not to talk forgetting to reset the state so that it cannot be called anymore). This is probably the most common way to do it, which does not mean that it is the best way, though... ;)
Excessive (lock for the whole computation) | Smarter (only lock while check-and-set the called state) |
---|
bool _called = false;
void ExcessiveLocking()
{
lock (this)
{
if (!_called)
{
_called = true;
}
}
}
|
bool _called = false;
void SmarterLocking()
{
bool called = _called;
lock(this)
{
called = _called;
_called = true;
}
if (!called)
{
}
}
|
Interlocked.Exchange
This is the less known approach. The uneasy thing is, that there is no Interlocked
functions for bool
. Not so nice to introduce states for something that is naturally handled by boolean value.
Interlocked with int state |
---|
const int NOTCALLED = 0;
const int CALLED = 1;
int _state = NOTCALLED;
void InterlockedCheck()
{
if (Interlocked.Exchange(ref _state, CALLED) == NOTCALLED)
{
}
}
|
ThreadSafeOneShotFlag
First the intended usage:
Explicit call | Mimic bool |
---|
ThreadSafeSingleShotFlag _calledOnce = false;
void SingleShotExplicit()
{
if (_calledOnce.CheckAndSetFirstCall)
{
}
}
|
ThreadSafeSingleShotFlag _calledOnce = false;
void SingleShotMimicBool()
{
if (!_calledOnce)
{
}
}
|
The above code mimics the bool
behavior: init
by false
(= not called yet). And allowing to check and set the called flag. What may be disturbing is the fact that the !
operator not only returns the value, but also sets it. No need to set the value to true
within the block. Therefore I provide the explicit access too.
Here goes the code:
public class ThreadSafeSingleShotFlag
{
private static int INIT = 0;
private static int CALLED = 1;
private int _state = INIT;
public bool CheckAndSetFirstCall
{ get { return Interlocked.Exchange(ref _state, CALLED) == INIT; } }
public static implicit operator ThreadSafeSingleShotFlag (bool called)
{
return new ThreadSafeSingleShotFlag() { _state = called ? CALLED : INIT };
}
public static bool operator !(ThreadSafeSingleShotFlag obj)
{
return obj.CheckAndSetFirstCall;
}
}
ThreadSafeSingleShotGuard
Instead of trying to mimic a boolean type (and therefore confusing developers), it might be advised to implement a pure wrapper for the Interlocked.Exchange
for a bool
. This makes it less vulnerable to unintended use.
First again the intended use:
Mimic Interlocked for bool |
---|
ThreadSafeOneShotGuard _guard = ThreadSafeOneShotGuard();
void GuardCheck()
{
if (_guard.CheckAndSetFirstCall)
{
}
}
|
And the respective implementation of the guard class:
public class ThreadSafeSingleShotGuard
{
private static int NOTCALLED = 0;
private static int CALLED = 1;
private int _state = NOTCALLED;
public bool CheckAndSetFirstCall
{ get { return Interlocked.Exchange(ref _state, CALLED) == NOTCALLED; } }
}
Important Note
The ThreadSafeSingleShotGuard
could be used in Dispose
pattern, where as the ThreadSafeSingleShotFlag
is a bit dangerous: A Dispose()
method should never allocate memory. With the ThreadSafeSingleShotFlag
, one could use _calledOnce = true;
, which would allocate a new object... which is bad. So, don't use the ThreadSafeSingleShotFlag
- use ThreadSafeSingleShotGuard
instead.
Summary
If Interlocked.Exchange
would provide bool
support, the whole tip would stop at the respective section above.
This single shot Guard is an attempt to mimic a thread safe one shot flag of the non thread safe form: if (!flag) { flag = true; ... }
. It mimics Interlocked.Exchange
for bool
to ensure that a block is called only once in a multi-threaded environment. If properly used, it could make code more robust. E.g. it could be used in the Dispose
pattern to make it thread safe.
ThreadSafeSingleShotGuard _disposeGuard = new ThreadSafeSingleShotGuard();
...
protected virtual void Dispose(bool disposing)
{
if (_disposeGuard.CheckAndSetFirstCall)
{
if (disposing)
...
}
}
I prefer the ThreadSafeSingleShotGuard
over the ThreadSafeSingleShotFlag
since the guard has less bells and whistles that can cause problems (e.g. no implicit conversion, no surprise in the not-operator (is not a pure function[^] in the given solution of the Flag
class), etc.).
Comments are very welcome, especially, how others deal with the issue of not having bool
support in the Interlocked
class. Which of the above alternatives (or any further) are in real life use?
History
- V1.0 2012-04-30 Initial version
- V1.1 2012-04-30 Enhanced summary
- V1.2 2012-04-30 Add
Guard
class to replace Flag
class to make the solution more robust - V1.3 2013-12-04 Fix "Smarter" lock code (thanks to tommy008)