Home Page
Blog

Debugging UI Objects Running Worker Threads

Google

The Problem

Have a look at the following form created in Visual Studio 2005. Not very exciting (or well written!), but hopefully fairly easy to understand. A new thread is created that increments var every half a second. This will work - although there won't be any way to see what is happening. The problem comes if we try to set a breakpoint on the line that increments var with var++. If the locals window is open in the debugger, then you will notice that it takes ages for the debugger to stop when the breakpoint is hit. If it isn't open, then the debugger will seem to 'hang' for several seconds when the locals window is opened.

   public partial class Form1 : Form
   {
      int var = 0;

      public Form1()
      {
         InitializeComponent();
         (new Thread(new ThreadStart(TimerThread))).Start();
      }

      private void TimerThread()
      {
         while (true)
         {
            var++;
            Thread.Sleep(500);
         }
      }
   }

What is Happening

The first time I saw this I could believe something so simple could go wrong. If you try it, you'll notice that if you press F5, the breakpoint will never bit hit ever again. Stepping with F10 also mysteriously doesn't step to the next statement. After hunting around on the internet I found out why this was happening. It turns out that the reason for this is behaviour is as follows. The breakpoint is hit. One of the consequences of this is that all the threads get suspended. Meanwhile the debugger tries to display this in the locals window. One of things that it does to achieve this is call all the property getters. It does this by hijacking the current thread from the object. That isn't the problem. The problem is that in order to get the value of user interface properties (like the form title), the call has to be marshalled onto the user interface thread. So that's what happens. Unfortunately, the UI thread is suspended, so it will never respond (not until the debugger resumes it anyway). The debugger eventually times out, and in the process kills the thread. But that was our thread! So unknown to us, our thread has been killed off, and so when we continue execution with F10 or F5, it will be minus our, now dead, thread.

How to Avoid This

The only way to prevent this is to make sure the properties don't get evaluated by the debugger. So if the locals window isn't open (and no properties are in an active watch window) then the properties won't be evaluated. That's not a ver good solution, so if you do find yourself in this situation, the best solution is to turn off property evaluation which is an option in tools -> options -> debugger -> general.

If you want more detail about this, I would recommend the following article, which also links to other articles on the subject.

Func-eval is evil

More Complete Example

Finally, for a more complete example, the following code is a more complicated version of the code above that updates a couple of textboxes on the form, so that you can see that something is happening. To recreate this, create a form with two textboxes and copy the code below into the form.

   public partial class Form1 : Form
   {
      int var1 = 0;
      int var2 = 0;

      Thread _timer;
      volatile bool _threadRunning;

      public Form1()
      {
         InitializeComponent();
         _timer = new Thread(new ThreadStart(TimerThread));
         _threadRunning = true;
         _timer.Start();
      }

      private void TimerThread()
      {
         while (_threadRunning)
         {
            var1++;
            Invoke(new MethodInvoker(delegate { textBox1.Text = var1.ToString(); }));
            var2 += 3;
            Invoke(new MethodInvoker(delegate { textBox2.Text = var2.ToString(); }));
            Thread.Sleep(500);
         }
      }

      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
         _threadRunning = false;
         if (!_timer.Join(1000))
            _timer.Abort();
      }
   }

By the way, note that you have to call Invoke() to update the textbox, because this has to be done on the user interface thread. .NET 1.1 did strange things if you didn't do this, whereas .NET 2.0 will throw an exception, which is a lot more helpful. The call to Invoke above uses anonymous methods which I think were a nice addition to .NET 2.0.