Threading is pretty scary. An solid grasp of threading, as a brilliant dev lead at Microsoft once told me, was one thing that consistently separated the "men/women from the boys/girls" in the engineering discipline.
Here's my attempt to sound adult. (Forgive me if my voice cracks.)
The Scenario: You have a bunch of threads running around doing work--in this case, adding random numbers to a collection. You have another thread with the aggregation job: take all of the numbers in the collection, add them to a running total, and clear out the collection.
This is a simplification of a problem I was faced with on my current gig: handle multiple "users" via separate WCF connections (coming in on separate threads). Aggregate their work requests and process them on a 'worker' thread. All while staying super responsive.
So you always have the one thread that's waiting around for work. How does it wait? The simple answer would be a while-true loop that's always polling.
That'll peg a thread pretty quick while doing a bunch of unneeded locking of your synchronization object. One could add some sleep statements to the loop to minimize the impact, but that feels like a bandage.
What one wants: a way to have the processing thread "wait" to until there actually is work to do.
Enter Monitor.Wait and Monitor.Pulse.
Mike Woodring does a pretty good job of explaining this (with a very funny title/analogy), but I really wanted a fully baked and un-contrived code sample.
So here it is. In exactly 100 lines of code.
Wait and Pulse both must be called in the context of a lock (Monitor.Enter/Exit) for the provided object.
Wait [Line 58] basically gives up the lock (and allows other code blocked by the lock to run) and waits until Pulse [Line 43] is called on another thread. The 'wait' thread then gets back in line for the lock and continues execution from where it left off.
It's a bit weird to accept that a lock for a given object is temporarily given up between the curly braces, but that's what happens.
The only hiccup I ran into was realizing that one really needs to be careful to make sure pulse is called enough, especially when shutting down. 'Wait' will wait forever if it doesn't get kicked.
A note: I go this all working with a WaitHandle initially (specifically, AutoResetEvent), but I realized it was a lot more involved and (after some research) a lot heavier than I needed. WaitHandle actually wraps some Win32 stuff that goes way beyond what I need for this scenario.
Anyway, here's my foray into non-WPF technical blogging.