views:

87

answers:

3

I have a list of threads, and I'm trying to get my main thread to wait for all of the threads in the list to terminate:

while (consumers.Count > 0) consumers[0].Join();

The problem is, this isn't atomic, and I can get an "Index was out of range" exception.

Is there any atomic way to check for the existence of consumers[0] and call consumers[0].Join()?

Note: I can't do

lock (myLocker) { if (consumers.Count > 0) consumers[0].Join(); }

because I don't want to block the other threads from accessing consumers while stuck in Join().

A: 

Assuming consumer[0] is only null or an instance of the class with the expected method, you could just do

while (consumers.Count > 0)
     {
     if (consumers[0] != null)
         {
         consumers[0].Join();
         }
     }
Matt
The point is that the list has *become empty after the count is checked*.
Jon Skeet
+5  A: 

Well if you don't have any synchronization applied at all but your list is being modified from multiple threads, you're in trouble already. Does everything else use myLocker? Assuming it does, how about:

while(true)
{
    List<Thread> copy;
    lock (myLocker)
    {
        copy = new List<Thread>(consumers);
    }
    if (copy.Count == 0)
    {
        break;
    }
    foreach (Thread thread in copy)
    {
        thread.Join();
    }
}

Note that this only accesses consumers while holding the lock, which is what you should do everywhere to achieve thread safety. It also calls Join on all the threads after taking a copy, rather than just doing one for each iteration.

If you know that threads won't be added to the list at this point (e.g. it's a thread pool which is draining) you can remove the loop and the check:

    List<Thread> copy;
    lock (myLocker)
    {
        copy = new List<Thread>(consumers);
    }
    foreach (Thread thread in copy)
    {
        thread.Join();
    }
Jon Skeet
Except my copy would immediately be stale.I imagine thread.Join() would fail if the thread has already terminated?
mbeckish
All other accesses to consumers are synchronized. Let me know if seeing any other code would help.
mbeckish
@mbeckish: Your copy will *always* be stale immediately you've accessed it - and your intuition about Thread.Join is incorrect, as documented: "If the thread has already terminated when Join is called, the method returns immediately." Given that you can't atomically "check thread state and call Join" any Join API which would throw an exception if the thread had already terminated is broken by design.
Jon Skeet
@mbeckish - No, look at my post (similar concern there), but: from http://msdn.microsoft.com/en-us/library/6b1kkss0.aspx you can read that "If the thread has already terminated when Join is called, the method returns immediately."
Ravadre
Bah, you were faster a bit Jon :).
Ravadre
Perfect. Thanks!
mbeckish
A: 

In your scenario, when you want to just wait for threads, why assume, that those threads themselves should remove from the list? Make sure, that only main thread can remove a thread from the list, after this thread has terminated, it won't change your design and you can be sure that you won't access element that it's not there. Your loop would look like this:


for (int i = 0; i < consumers.Count; ++i)
    consumers[i].Join();
//Now that you have joined everyone, just remove reference to your list
consumers = null;
Ravadre