I received my October 2004 issue of MSDN Magazine today… woo hoo! I of course flipped to .NET Matters to see how it looked, and I noticed that somewhere during the process of putting out the issue I managed to omit a line of code from one of my code figures… oops! It’ll be fixed when the content goes online in several weeks, but in the meantime…
In the first column of Figure 3 on page 133 (don’t you love it when a magazine has over 130 pages… 144 this month!), there is a block of code that looks like:
_remainingWorkItems = 1;
The line after that was omitted and should read:
else Interlocked.Increment(ref _remainingWorkItems);
What’s the benefit of that line? It’s not the focus of the column so I gloss over it, but without this line all is well unless WaitOne is called more than once before all work items have completed. So if you use the overload of WaitOne that takes no parameters (and thus wait until all items have completed), everything should work correctly. If, however, you use an overload that takes a timeout parameter, and that timeout expires before the work items have completed, you’ll have a problem should you call WaitOne again. The DoneWorkItem method called at the beginning of the function will have already decremented _remainingWorkItems in order to account for _remainingWorkItems initially being 1 (the reason for which is explained in the column). The next time WaitOne is called, however, DoneWorkItem will be called again to remove that initial value of 1, but it was already removed, so _remainingWorkItems will be erroneously decremented. This could result in _done being set too soon, causing ThreadPoolWait to erroneously show that all work items had completed. Thus, the Interlocked.Increment line is necessary to bump the value back up so that the next time WaitOne is called, _remainingWorkItems will still be valid. It’s fine, too, if by the time this line is executed all of the work items have finished. The next time WaitOne is called, the value of _remainingWorkItems will be 1 (as a result of the increment up from 0), and DoneWorkItem will remove that value and set _done; it doesn’t matter that _done will have already been set when the last work item finished, since setting an already set ManualResetEvent is for all intents and purposes a noop.
Note that in the ‘if’ block above I don’t have to use the Interlocked class to set the value of _remainingWorkItems to 1 because at that point rv == true, which means that all work items have completed and no other threads will be mucking with the value.