First, I'm not sure if I should "Answer my own question" or use a comment for this, but here goes:
My understanding is that volatile prevents code/memory optimizations from moving the accesses to my result variables (and the completed boolean) such that the the thread that reads the result will see upt-to-date data.
You wouldn't want the _completed boolean made visible to all threads after the Set() due to compiler or emmpry optimaztions/reordering. Likewise, you wouldn't want the writes to the results _a, _b, _c being seen after the Set().
EDIT: Further explaination/clarification on the question, in regards to items mentioned by Matt Davis:
Finally, i should point out that
whenever a thread calls an interlocked
method, the CPU forces cache
coherency. So if you are manipulating
variables via interlocked methods, you
do not have to worry about all of this
memory model stuff. Furthermore, all
thread synchronization locks (Monitor,
ReaderWriterLock, Mutex, Semaphone,
AutoResetEvent, ManualResetEvent,
etc.) call interlocked methods
internally.
So it would seem that, as wekempf
pointed out, that the result variables
do not require the volatile keyword in
the example as shown since the
ManualResetEvent ensures cache
coherency.
So you are both in agreement that such an operation takes care of caching between processors or in registers etc.
But does it prevent reording to guarantee such that BOTH the results are assigned before the completed flag, and that the completed flag is assigned true before the ManualResetEvent is Set?
First, my initial assumption was that
the background thread would
potentially run multiple times. I
obviously overlooked the name of the
class (OneUseBackgroundOp)! Given that
it is only run once, it is not clear
to me why the DoSomething() function
calls WaitOne() in the manner that it
does. What is the point of waiting
initialWaitMs milliseconds if the
background thread may or may not be
done at the time DoSomething()
returns? Why not just kickoff the
background thread and use a lock to
synchronize access to the results
variables OR simply execute the
contents of the Task() function as
part of the thread that calls
DoSomething()? Is there a reason not
to do this?
The concept of the sample is to execute a possibly long-runnig task. If the task can be completed within an exceptable amount of time, then the calling thread will get access to the result and continue with normal processing. But sometime a task can take quite a long time and the claiing thread cannot be blocked for that period and can take reasonable steps to deal with that. That can include checking back later on the operation using the Completed property.
A concrete example: A DNS resolve is often very quick (subsecond) and worth waiting for even from a GUI, but sometimes it can take many many seconds. So by using a utility class like the sample, one could gets a result easily from the point-of-view of the caller 95% of the time and not lock up the GUI the other 5%. One could use a Background worker, but that can be overkill for an operation that the vast majority of the time doesn't need all that plumbing.
Second, it seems to me that not using
some kind of locking mechanism on the
results variables is still a bad
approach. True, it is not needed in
the code as shown.
The result (and completed flag) data is meant to be write-once, read-many. If I added a lock to assign the results and flag, I'd also have to lock in my result getters, and I never liked seeing getters lock just to return a data point. From my reading, such fine-grained locking is not appropriate. If an operation has 5 or 6 results, the caller has to take and release the lock 5 or 6 times needlessly.
But at some point
down the road, another thread may come
along and try to access the data. It
would be better in my mind to prepare
for this possibility now rather than
try to track down mysterious behavior
anomalies later.
Because I have a volatile completed flag that is guarenteed to be set before the volatile results are, and the only access to the results is through the getters, and as mentioned in the smaple, an exception is thrown if the getter is called and the operation is not yet complete, I'd expect that the Completed and result getters CAN be invoked by a thread other than the one that called DoSomething(). That's my hope anyway. I believe this to be true with the volatiles anyway.