How to create a thread safe method that can be called only once?

I am trying to write a thread safe method that can only be called once (for an instance of an object). An exception must be thrown if it was called earlier.

I came up with two solutions. Are they both right? If not, then what's wrong with them?

  • With lock :

     public void Foo() { lock (fooLock) { if (fooCalled) throw new InvalidOperationException(); fooCalled = true; } … } private object fooLock = new object(); private bool fooCalled; 
  • With Interlocked.CompareExchange :

     public void Foo() { if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1) throw new InvalidOperationException(); … } private int fooCalled; 

    If I am not mistaken, this solution has the advantage that it is not blocked (which in my case does not matter) and that this requires fewer private fields.

I am also open to informed opinions, the solution of which should be preferred, and to further suggestions if there is a better way.

+7
source share
3 answers

Your Interlocked.CompareExchange solution looks better, and (as you said) is blocked. It is also significantly less complicated than other solutions. Locks are quite heavy, and CompareExchange can be compiled to one CAS cAS instruction. I say go with it.

+5
source

The double lock flag is what you need:

That's what you need:

 class Foo { private object someLock = new object(); private object someFlag = false; void SomeMethod() { // to prevent locking on subsequent calls if(someFlag) throw new Exception(); // to make sure only one thread can change the contents of someFlag lock(someLock) { if(someFlag) throw new Exception(); someFlag = true; } //execute your code } } 

In general, when you encounter similar problems, try and follow well-known patterns like the ones above. This makes it recognizable and less error prone, since you are less likely to miss something when you follow the pattern, especially when it comes to streaming.
In your case, the first, if it does not make much sense, but often you need to execute the actual logic, and then set the flag. The second thread will be blocked while you execute your (perhaps quite expensive) code.

About the second example:
Yes, that’s right, but don’t make it more complicated than that. You must have very good reasons not to use a simple lock, and in this situation it makes the code more complicated (since Interlocked.CompareExchange() less known) without achieving anything (because you indicated that the lock is less than the lock to set the Boolean flag on the in fact, it is not profitable).

0
source
  Task task = new Task((Action)(() => { Console.WriteLine("Called!"); })); public void Foo() { task.Start(); } public void Bar() { Foo(); Foo();//this line will throws different exceptions depends on //whether task in progress or task has already been completed } 
-one
source

All Articles