Click here to Skip to main content
12,509,269 members (62,314 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

33.1K views
7 bookmarked
Posted

Implement Thread Safe One-shot Bool Flag with Interlocked.Exchange()

, 4 Dec 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Wrapping Interlocked.Exchange to mimic a thread safe one-shot bool flag

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;
        // ...do once... -- maybe not! --
    }
}

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;
            // ...do once...
        }
    }
}
bool _called = false;
//...
void SmarterLocking()
{
    bool called = _called;
    lock(this)
    {
        called = _called;
        _called = true;
    }
    if (!called)
    {
        // ...do once...
    }
}

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)
    {
        // ...do once...
    }
}

ThreadSafeOneShotFlag

First the intended usage:

Explicit callMimic bool
ThreadSafeSingleShotFlag _calledOnce = false;
//...
void SingleShotExplicit()
{
    if (_calledOnce.CheckAndSetFirstCall)
    {
        // ...do once...
    }
}
ThreadSafeSingleShotFlag _calledOnce = false;
//...
void SingleShotMimicBool()
{
    if (!_calledOnce)
    {
        // ...do once...
    }
}

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:

/// <summary>Flag-like class that returns at most once the value true.</summary>
/// <example>
/// <code>
/// ThreadSafeSingleShot calledOnce = false;
/// ...
/// if (!calledOnce) // returns with the first call the initial value 
/// and then returns always false
/// {
///    ...
/// }
/// </code>
/// </example>
public class ThreadSafeSingleShotFlag
{
    private static int INIT = 0;
    private static int CALLED = 1;
    private int _state = INIT;
    /// <summary>Explicit call to check and set if this is the first call</summary>
    public bool CheckAndSetFirstCall
    { get { return Interlocked.Exchange(ref _state, CALLED) == INIT;  } }
    
    /// <summary>usually init by false</summary>
    public static implicit operator ThreadSafeSingleShotFlag (bool called)
    {
        return new ThreadSafeSingleShotFlag() { _state = called ? CALLED : INIT };
    }
    /// <summary>if init to false, returns true with the first call, then always false - 
    /// if init to true, always returns false.</summary>
    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)
    {
        // ...do once...
    }
}

And the respective implementation of the guard class:

/// <summary>
/// Thread safe enter once into a code block:
/// the first call to CheckAndSetFirstCall returns always true,
/// all subsequent call return false.
/// </summary>
public class ThreadSafeSingleShotGuard
{
    private static int NOTCALLED = 0;
    private static int CALLED = 1;
    private int _state = NOTCALLED;
    /// <summary>Explicit call to check and set if this is the first call</summary>
    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)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andreas Gieriet
Founder eXternSoft GmbH
Switzerland Switzerland
I feel comfortable on a variety of systems (UNIX, Windows, cross-compiled embedded systems, etc.) in a variety of languages, environments, and tools.
I have a particular affinity to computer language analysis, testing, as well as quality management.

More information about what I do for a living can be found at my LinkedIn Profile and on my company's web page (German only).

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionNice one, a 5 Pin
HaBiX4-Dec-13 8:35
memberHaBiX4-Dec-13 8:35 
AnswerRe: Nice one, a 5 Pin
Andreas Gieriet4-Dec-13 11:53
professionalAndreas Gieriet4-Dec-13 11:53 
GeneralThoughts Pin
PIEBALDconsult4-Dec-13 8:05
professionalPIEBALDconsult4-Dec-13 8:05 
GeneralRe: Thoughts Pin
Andreas Gieriet4-Dec-13 11:50
professionalAndreas Gieriet4-Dec-13 11:50 
QuestionDispose should NOT be thread-safe Pin
William E. Kempf4-Dec-13 6:29
memberWilliam E. Kempf4-Dec-13 6:29 
AnswerRe: Dispose should NOT be thread-safe Pin
Andreas Gieriet4-Dec-13 11:48
professionalAndreas Gieriet4-Dec-13 11:48 
BugLocked Access Smater will never do once Pin
tonny0084-Dec-13 2:32
membertonny0084-Dec-13 2:32 
GeneralRe: Locked Access Smater will never do once Pin
Andreas Gieriet4-Dec-13 4:36
professionalAndreas Gieriet4-Dec-13 4:36 
QuestionWhy not just use C# volatile bool for the flag? Pin
Ivan Pechorin24-Nov-13 11:32
memberIvan Pechorin24-Nov-13 11:32 
AnswerRe: Why not just use C# volatile bool for the flag? Pin
Andreas Gieriet24-Nov-13 22:04
professionalAndreas Gieriet24-Nov-13 22:04 
GeneralMy vote of 3 Pin
jfriedman30-Apr-12 13:27
memberjfriedman30-Apr-12 13:27 
GeneralRe: My vote of 3 Pin
Andreas Gieriet24-Nov-13 22:06
professionalAndreas Gieriet24-Nov-13 22:06 
GeneralRe: My vote of 3 Pin
HaBiX4-Dec-13 8:39
memberHaBiX4-Dec-13 8:39 
GeneralRe: My vote of 3 Pin
jfriedman4-Dec-13 9:36
professionaljfriedman4-Dec-13 9:36 
QuestionClever but potentially misleading Pin
Chris Maunder30-Apr-12 1:55
adminChris Maunder30-Apr-12 1:55 
AnswerRe: Clever but potentially misleading Pin
Andreas Gieriet30-Apr-12 4:08
memberAndreas Gieriet30-Apr-12 4:08 
GeneralRe: Clever but potentially misleading Pin
Chris Maunder30-Apr-12 6:31
adminChris Maunder30-Apr-12 6:31 
GeneralRe: Clever but potentially misleading (Not bad but why not using Monitor?) Pin
Member 342354613-Dec-12 17:43
memberMember 342354613-Dec-12 17:43 
GeneralRe: Clever but potentially misleading (Not bad but why not using Monitor?) Pin
Member 342354613-Dec-12 17:58
memberMember 342354613-Dec-12 17:58 
GeneralRe: Clever but potentially misleading (Not bad but why not using Monitor?) Pin
Andreas Gieriet16-Dec-12 12:48
memberAndreas Gieriet16-Dec-12 12:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160927.1 | Last Updated 4 Dec 2013
Article Copyright 2012 by Andreas Gieriet
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid