views:

45

answers:

2

I've noticed a common pattern in some code I'm writing so I decided to extract it into a class. However, since starting to use this class I've been having a problem where every once in a while the program will hang indefinitely, and, from what I can tell from debugging, this class seems to be the cause. Could sometime tell me what I'm doing wrong? Thank you for your time.

Updated code:

class ParallelTaskWaiter
{
    int numTasksRunning;
    private readonly object completeLock = new object();

    public void WaitFor(ThreadStart action)
    {
        Interlocked.Increment(ref numTasksRunning);
        ThreadPool.QueueUserWorkItem(delegate
        {
            try { action(); }
            finally
            {
                if (Interlocked.Decrement(ref numTasksRunning) == 0)
                {
                    lock (completeLock)
                    {
                        Monitor.PulseAll(completeLock);
                    }
                }
            }
        });
    }

    public void Wait()
    {
        lock (completeLock)
        {
            if (Interlocked.CompareExchange(ref numTasksRunning, 0, 0) == 0) return;
            Thread.SpinWait(1);
            Monitor.Wait(completeLock, Timeout.Infinite);
        }
    }
}
A: 

WAG here... Change

if (Interlocked.Decrement(ref numTasksToComplete) == 0)

to

if (Interlocked.Decrement(ref numTasksToComplete) <= 0)

Its the safer bet, no matter what.

In addition, while you are interlocking the increment, you are still incrementing THEN queueing the work item. Incrementing and queueing should be atomic. I'd also use a lock instead of an interlocked increment.

Will
doing that would surely mean there is a synchronisation error elsewhere...
Mitch Wheat
@mitch WAG = wild ass guess. Hard to debug multithreaded code from a sample, of course. Still looking for stuff, however.
Will
Thanks for the reply Will. For the `lock` portion of your reply do you mean add a private object member to the class and lock on that around both the increment and QueueUserWorkItem?
George
@george yes. Instead of interlocked incrementing, lock around both increment and queue, and both instances where you decrement and Set(). I assume you are blocking disposal until everything is done. That pattern is a little... not good. Both the increment/decrement and the action depending on the current state of the value is atomic and must be done within the scope of a lock. Also, what happens when the count is zero and somebody queues another user work item? In the current code, nothing happens, and the dispose does not block. I'd rethink this class and its purpose.
Will
Could you take a look at my updated code? I think I've addressed the issues you brought up. Thanks.
George
@george you need to lock on each access of the variable. You should lock the entire WaitFor method. Also, with a lock Interlocked is not necessary. Just increment the var normally. Try that and see what happens!
Will
Thanks for all you help.
George
@George np, but I think the design is a little lacking. I'd either go with an immutible blocking collection holding Actions that are awaiting completion, or require callers to provide *all* methods to be run asynchronously at once (practically the same thing), then shut down business after they are done. This current class seems a bit too smelly. The bad thing about multithreading is that if you allow people to be too flexible it makes your code exponentially harder to ensure it is safe. I think its better to restrict up front then try to be flexible in the end. Good luck, no matter.
Will
+1  A: 

Shouldn't that be:

private int numTasksToComplete = 0;

There isn't a task to begin with.

Mitch Wheat
Without that I think it would be possible for a thread to complete and find that Interlocked.Decrement(ref numTasksToComplete) == 0 was true even though there are more tasks that haven't been added yet.
George
It would be truly horrible if the completely obvious answer was the correct one.
Will
@george more the reason to lock rather than use an interlocked increment/decrement.
Will