I basically have a counter variable that is decremented and frequently polled. There are three options to implement that, that I can think of:
Number 1:
private volatile int count;
public void Finished() {
Interlocked.Decrement(ref count);
}
public bool Done() {
return count == 0;
}
Number 2:
private int count;
public void Finished() {
Interlocked.Decrement(ref count);
}
public bool Done() {
return Volatile.Read(ref count) == 0;
}
Number 3:
struct AtomicInteger {
private int value;
public int Get() {
return Volatile.Read(ref value);
}
// other methods
}
// In actual class:
private AtomicInteger count;
public bool Done() {
return count.Get() == 0;
}
The first one gives a compiler warning because of passing a volatile variable by reference. On the other hand it's clear from the variable definition that the variable is accessed concurrently which is clearly a warning sign to anybody looking at the code. If I ever decide that I actually need a long, this method does not work at all any more.
The second one doesn't give any warnings, but just from reading private int count
it's not clear that the variable is used concurrently, so this has to be explicitly stated with a comment (I actually had a bug where I used a normal read in an assertion instead of Volatile.Read
- such an easy thing to overlook.)
Changing the variable to a long on the other hand does not introduce any additional work.
I do like number 3 and it shouldn't result in any performance overhead compared to the other solutions after inlining. Downside: Code duplication for every primitive. On the other hand the code is simple enough and can be copy-pasted without fear of problems and is unlikely to ever change.
Any comments on advantages/disadvantages of each method that I've overlooked or what would be considered idiomatic in C#?
Interlocked.Decrement()
; if it is zero, call a callback function (or delegate).CountdownEvent
class (since .NET 4.0), which is probably based on Win32HANDLE
.Task
, and useTask.WhenAll(Task[])