Can this cause deadlock? BeginInvoke() & thread.Join()

1.5k views Asked by At

I have this code that many threads can call to update the GUI:

MethodInvoker del = () => { lblInfo.Text = tmp; };
lblInfo.BeginInvoke(del);

(lblInfo is created by the GUI thread)

I also have this method called at button click executed by the GUI thread:

public void Stop()
{
    isStopping = true;
    crawler.Join();
    foreach (Thread t in txtWorkers)
    {
        t.Join();
    }
    indexer.Join();     
    lblStatus.Text = "Stopped";
    lblInfo.Text = "";
}

1 time over 100 run the program deadlock at Stop button click. I was not debugging when i saw the deadlock so i can't be sure about the state of the various threads but i'm almost sure that all the threads i'm joining will eventually reach the point where they check for isStopping value and terminate. This leads me to think that there may be a problem with the BeginInvoke but can't really find it. It should be async so threads calling it (crawler & indexer) should not block. What happens if the GUI thread is executing Stop() and also must execute a call from BeginInvoke? Could this be the problem? Is there something i can't see about the thread i'm joining?

EDIT: What the code looks like after the suggested changes:

public void Stop()
{
    /*
     ...disable GUI
     */

    isStopping = true; // Declared as volatile
    lblStatus.Text = "Stopping...";

    // Creating a thread that will wait for other threads to terminate
    Task.Factory.StartNew(() =>
    {    
        crawler.Join();
        foreach (Thread t in txtWorkers)
        {
            t.Join();
        }
        indexer.Join();

        // Adjust UI now that all threads are terminated
        MethodInvoker del = () =>
        {
            /*
            ...enable GUI
            */
            lblStatus.Text = "Not Running";
            isStopping = false;
        };
        lblStatus.BeginInvoke(del);
    });
}

It seems to be working, i hope that deadlock is gone...

1

There are 1 answers

2
Jon Skeet On BEST ANSWER

I don't think it should be a problem, because you're using BeginInvoke rather than Invoke - the background threads will just proceed past that line without waiting for the GUI to catch up. If you're using Control.Invoke anywhere, that could cause a deadlock.

More importantly, using Join in your GUI thread is fundamentally a bad idea - the UI will be frozen until everything's finished. It would be better to disable any controls which could start anything new, set your isStopping flag, and then create a new thread to wait for all the threads to stop - and when all the threads have finished, then update the UI with BeginInvoke again. (If you're using .NET 4.5 you could also use an asynchronous method for this, creating and awaiting a task to wait for all the threads.)

Finally, if isStopping is just a bool field, there's no guarantee that your background threads will "see" the change from the UI thread. It's possible that making the field volatile would fix this, but the precise meaning of volatile scares me. An alternative would be to use the Interlocked class, or make it a property which obtains a lock for both reading and writing - that ensures appropriate memory barriers are in place.