(Note: I think your best bet is Parallel.Invoke() - see below in this answer.)
What you do will work fine, so the problem may be due to the fact that for some reason one of your threads is blocked.
You should be able to debug this message quite easily - you can attach a debugger and break into the program, and then look at the call stack to find out which threads are blocked. Get ready to scratch your head if you discover a race condition though!
Another thing to know about is that you cannot do the following:
myEvent.Set(); myEvent.Reset();
nothing (or very little) between .Set() and .Reset() . If you do this when multiple threads are waiting on myEvent , some of them will skip the set event! (This effect is poorly documented on MSDN.)
By the way, you should not ignore exceptions - always register them in some way, at least.
(This section does not answer the question, but may provide some useful information)
I also want to mention an alternative way to wait for threads. Since you have a set of ManualResetEvents, you can copy them into a simple array and pass it to WaitHandle.WaitAll() .
Your code might look something like this:
WaitHandle.WaitAll(eventList.ToArray());
Another approach to waiting for all threads to complete is to use a CountdownEvent . It becomes signaled when the countdown reaches zero; you start counting the number of threads, and each thread signals this when it exits. Here is an example here .
Parallel.Invoke()
If your threads do not return values, and all you want to do is start them and then start the start thread to exit them, then I think Parallel.Invoke() would be the best way for everyone. This avoids the need to synchronize yourself.
(Otherwise, as svick says in the comments above, use Task , not the old thread classes.)