Winforms and threading

There has been a recent discussion internally amongst the ADC’s in my region* about threading and WinForms.  I originally cut my teeth in Windows programming by writing a multi-threaded NT Service that ran on a Alpha based Windows NT 3.51 system (who coined the term “Wintel”?  I had a copy of Visual C++ 4.0 for MIPS and DEC Alpha – definitely non-Intel processors running Windows NT.  It was the market (or lack thereof) that pulled support for those processors).  So I learned early on about the joys of mutexes and critical sections.  All that learning went away as I moved to more lucrative Visual Basic 5.0 & 6.0 work and the multi-threading center of my brain became “soft”.  The recent mental exercise of tackling threading issues in WinForms has been great fun.

The challenge with WinForms is that underneath it all, all of the System.Windows.Forms.Button’s and stuff are Windows® intrinsic windows.  These can only generally receive messages on the thread that they were created on, so if you are performing some really long operation (say, calling a web service – or in a demo calling System.Threading.Thread.Sleep(5000);) that thread cannot process messages for the UI.  This manifests itself as an unresponsive application; several portions of the screen won’t paint, mouse clicks don’t register right away, and the user finally closes the application and sends us a min-hang dump via the “you closed an unresponsive application” dialog.  The best solution to this is to make your web service call (or whatever was taking so long) on another thread.  There is an excellent article by Ian Griffiths in the June 2003 issue of MSDN Magazine (also online here) on how to do this.

More or less (read: a highly simplified way of explaining this is), you define a delegate with the method signature you want to execute on another thread.  The create an instance of that delegate pointing to the method you want to run and call delegatename.Invoke passing an AsyncCallback object.  Your method then runs on a thread from the managed thread pool, and when the method completes it will call into the AsychCallback method.  Here you must call EndInvoke which will not only clean up a lot of the thread stuff, it will return the return value of your method.  Keep in mind you are still running on the thread pool thread, so you cannot update the UI directly.  You need to use the Control.Invoke method to marshal your call back to the UI thread.  Some code may help explain this.

 private void calcBtn_Click(object sender, System.EventArgs e)
{
    string input = "On UI Thread";
    this.resultLbl.Text = "";
    
    DoWorkDelegate work = new DoWorkDelegate(DoWork);
    work.BeginInvoke(input, new AsyncCallback(WorkDone), null);
}
public delegate string DoWorkDelegate(string input);
public string DoWork(string input)
{
    System.Threading.Thread.Sleep(5000);
    return "Returned from thread: " + input;
}
public void WorkDone (IAsyncResult iar)
{
    AsyncResult ar = (AsyncResult)iar;
    DoWorkDelegate myThreadProc = (DoWorkDelegate)ar.AsyncDelegate;
    string result = myThreadProc.EndInvoke(iar);
    UpdateUIDelegate updateDelegate = new UpdateUIDelegate(UpdateFromWork);
    object [] args = {result};
    this.Invoke(updateDelegate, args);
}
public delegate void UpdateUIDelegate (string updateStr);
public void UpdateFromWork (string updateStr)
{
    this.resultLbl.Text = updateStr;
}

If you set break points in the various methods and watch the “Threads” window in VS.NET, you’ll see what code is being run where.  Something to note is that you should see a two threads running, however some of us have sometimes observed an extra thread with no code associated with it (this is what sparked our discussion to begin with).  Nobody seems to know what this thread is, and a dump using ADPlus shows that it is not actually there.  We are chalking it up as a weird VS.NET bug – if someone has a better explanation, I’d love to hear it.  Many thanks to Brian MacKay for showing me how to do this sort of work with delegates.

* Geographically speaking, I am part of the Central Region of the US subsidiary (there are two others, aptly named East and West).  This is about as far as the services organization (meaning Premier Support and Microsoft Consulting Services) in Microsoft get broken down.  The sales organization does go one level of granularity further into districts.  Services used to align to districts, but a re-org a year or two ago rolled us up into a regional model.  District-wise, I’d be considered in the Midwest district which is Wisconsin, Illinois, and Indiana.