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...
I don't think it should be a problem, because you're using
BeginInvoke
rather thanInvoke
- the background threads will just proceed past that line without waiting for the GUI to catch up. If you're usingControl.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 yourisStopping
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 withBeginInvoke
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 abool
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 theInterlocked
class, or make it a property which obtains a lock for both reading and writing - that ensures appropriate memory barriers are in place.