2

I am facing an issue with communication between threads in a C#.NET application. Hope someone will guide me in the right direction about the possible solutions.

I have an application in C#.NET.It is a windows form application. My application has two threads - One thread is the main thread (UI thread) and the other one is the child thread. Lets call the child thread the "workerThread" There is only one form used in the application.Lets call this form the "MainForm"

The child thread is started when the MainForm loads (used the form's "Load" event handler to start the thread)

In the MainForm class, I have a variable named "stopWork" which is a public boolean variable and it serves as a flag to indicate whether the child thread should continue working or should it stop

I have another class (besides the MainForm class) which contains the method that I execute in the the child thread. Lets call this second class the "WorkerClass". I pass a reference to the current form (the MainForm) into the constructor of the "WorkerClass"

I have a button "stop" in the main form which sets "stopWork" to "true" if its clicked and then calls "workerThread.Join()" to wait for the child thread to finish excecution.

In the child thread, the method "doWork" keeps checking the status of "parentForm.stopWork" inside a for loop. If "stopWork" is set to "true" then the loop breaks and subsequently the method ends.

Now, the issue is, once I am clicking the "stop" button ,the application hangs.

I am pasting parts of the code below so that it is easier to understand :

public partial class MainForm : Form
{
    Thread workerThread = null;
    ThreadStart workerThreadStart = null;
    WorkerClass workerClass = null;

    public bool stopWork = true;

    /*.......... some code ............*/

    private void MainForm_Load(object sender, EventArgs e)
    {
        workerThreadStart = new ThreadStart(startWork);
        workerThread = new Thread(workerThreadStart);
        stopWork = false;
        workerThread.Start();
    }

    private void startWork()
    {
        workerClass = new WorkerClass(this);
    }

    private void buttonStop_Click(object sender, EventArgs e)   //"stop" button
    {
        if (workerThread != null)
        {
            if (workerThread.IsAlive == true)
            {
                stopWork = true;
                workerThread.Join();
            }
        }
    }

    /*.......... some more code ............*/

}

public class WorkerClass
{
    MainForm parentForm=null;

    /*......... some variables and code ........*/

    public WorkerClass(MainForm parentForm)
    {
        this.parentForm=parentForm;
    }

    /* .............. some more code ...........*/

    public void doWork()
    {
       /*.......... some variables and code ...........*/

       for(int i=0;i<100000;i++)
       {
           // ** Here is the check to see if parentForm has set stopWork to true **
           if(parentForm.stopWork==true)
              break;

           /*......... do some work in the loop ..........*/


       }

    }

    /********* and more code .........*/
}

I think I may know where the problem lies. The problem is in the "doWork" method in the child thread trying to access "stopWork" variable in the parent form when already the parent form is blocked by calling the "workerThread.Join()" method. So ,I think this is a "deadlock" problem.

Am I right in identifying the problem ? Or am I wrong and the problem lies somewhere else ?

In case this is indeed a deadlock, what are the possible solutions to solve this ?

I did a bit of googling and found lots of resources on thread synchronisation and how to avoid deadlocks. But I could not understand how to apply them specifically to my problem.

I would really appreciate any help or guidance on resolving this issue.

sandyiscool
  • 561
  • 2
  • 9

2 Answers2

3

Yes, the code you wrote is highly vulnerable to deadlock. The BackgroundWorker class is especially prone to cause this kind of deadlock.

The problem is located in code we can't see in your snippet, the WorkerClass. You are surely doing something there that affects the UI in one way or another, always the primary reason to consider creating a thread in the first place. You probably use Control.Invoke() to have some code run on the UI thread and update a control. Perhaps also to signal that the worker thread is completed and, say, set the Enable property of a button back to true.

That's deadlock city, such code cannot run until the UI thread goes idle, back to pumping its message loop. It will never be idle in your case, it is stuck in Thread.Join(). The worker thread can't complete because the UI thread won't go idle, the UI thread can't go idle because the worker thread isn't finishing. Deadlock.

BackgroundWorker has this problem too, the RunWorkerCompleted event cannot run unless the UI thread is idle. What you need to do is not block the UI thread. Easier said than done, BGW can help you get this right because it runs an event when it completes. You can have this event do whatever you now do in the code past the Thread.Join() call. You'll need a boolean flag in your class to indicate that you are in the 'waiting for completion' state. This answer has relevant code.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks for the insight. You were spot on.I am indeed using Invoke on a rich text box control to write messages to the UI !!! :-) – sandyiscool Apr 02 '11 at 15:18
  • 1
    I think the main point to take away from Hans' answer, and similar responses, is that threading is hard to get right. You absolutely need full knowledge of what you're doing, what other threads are doing, and how they interact. – Lasse V. Karlsen Apr 02 '11 at 21:18
  • You seem to be saying both that BackgroundWorker would make this problem worse and that BackgroundWorker is the ultimate solution to this problem. – MusiGenesis Apr 03 '11 at 01:47
  • Yes, if the reason for the deadlock is unclear, BGW is worse because the RunWorkerCompleted event *guarantees* deadlock. Solving it is effectively done with BGW because it has a RunWorkerCompleted event, allowing Thread.Join() to be removed. No contradiction. – Hans Passant Apr 03 '11 at 01:54
  • Thanks a lot for the valuable insight. I found a work around to the problem. Essentially, my code was calling Invoke on a rich text box control even after the GUI thread was waiting on the Join() call on the child thread. So,I used a boolean flag to indicate the "waiting for completion" stage and whenever the flag was "true" then instead of passing message to the rich text box, I collected all messages from the child thread in a StringCollection object.After the child thread was complete,I printed the messages that were stored in the String Collection object. – sandyiscool Apr 03 '11 at 13:47
2

Use a BackgroundWorker for this task instead. When you want to stop the task's execution, call the background worker's CancelAsync method.

Generally speaking, rolling your own threading code (on any platform) is a recipe for disaster if you don't have an expert-level understanding of multithreading (and even then it's still dangerous).

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • BackgroundWorker is surely an option to consider. But, what if I want to handle the task with my own thread ? How to go about it then ? – sandyiscool Apr 02 '11 at 12:01
  • 2
    This is like hearing someone say "calling the fire department is surely an option to consider. But, what if I want to put out my burning house by myself?" I'd suggest working through these tutorials: http://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx – MusiGenesis Apr 02 '11 at 12:07
  • LOL..Well, you surely wont call a fire department to put out a "kitchen fire" :-) Jokes apart, I like to handle thread by myself unless its very complex multi-threading scenario. This is a simple "start-thread > check condition > stop-thread" case. It doesn't even involve any shared resource that the threads access. All it needs is a way to check a flag without hitting a deadlock. – sandyiscool Apr 02 '11 at 12:15
  • 1
    I think your app is locking up when you click the Stop button because you're joining `workerThread`, which is a thread you keep alive indefinitely because you're maintaining it as a class-level variable. `doWork()` is never called (unless it's called in one of the snipped parts), and it wouldn't matter if it were. Again, I would suggest working through the tutorials (actually running the code) and get a better feel for how everything should work. – MusiGenesis Apr 02 '11 at 12:24
  • Here's another one: http://msdn.microsoft.com/en-us/library/7a2f3ay4(v=vs.80).aspx#Y170 This seems to be more or less exactly what you're doing. A particular tip is to use the `volatile` keyword for your `stopWork` variable. `volatile` is a simple and cheap way of synchronizing (that only works for a bool). – MusiGenesis Apr 02 '11 at 12:26
  • Even simpler, now that I look at it: just get rid of the `workerThread.Join()` line in your stop button click and make `stopWork` volatile. – MusiGenesis Apr 02 '11 at 12:30
  • @MusiGenesis : Thank you for the help.The links you provided are great for going in depth into multi-threading. – sandyiscool Apr 03 '11 at 13:40