Threading Tips and Tricks

Have you ever had trouble designing or debugging multi-threaded applications?


Asynchronous programming can be a struggle without a toolbox of techniques and tips to help. Let me show you what I’ve learned to make multi-threading in C# faster to implement and easier to manage!


Use the Threads Window

Visual Studio (Standard and up) has a tool for viewing running threads, aptly named the “Threads” window (Start your application in the debugger and then click on Debug->Windows->Threads [or CTRL+D, T]. Break execution to activate the window. From there, you can see which threads are running and minimal information about each one.


The really useful feature about the Threads window is that it allows you to update the call stack window to the thread of your choosing. Just double click on another thread to synchronize the call stack window with the new thread.


Another useful feature is the ability to Freeze and Unfreeze threads without using the Immediate window. Just right click on a given thread to pull up the relevant menu.

Freezing other threads is exceptionally useful if you are trying to step through execution in just one thread and it’s safe to temporarily ignore the others.

Without freezing the other threads, the debugger is likely to move you around from the context of one thread to another while stepping through, wasting your time and energy in the process.


Name your threads.

Use the Thread.Name property to assign a descriptive name to each of your threads. For Example:

   1: Thread YearEndProfitReportThread = new Thread(CreateYearEndProfitReport);
   2: YearEndProfitReportThread.Name = "Year End Profits Calculator";
   3: YearEndProfitReportThread.Start();


Now, in the Threads window you will be able to see the name you assigned instead of “<No Name>”.


This is useful while debugging, because of instead of selecting each thread until you find the right one you can jump to exactly the one you need.


Leverage the power of C# 3.0

Language specification 3 introduced a few powerful tools such as object initializers and lambda expressions that can apply to threading. These are very powerful tools that obviously need to weighed carefully with readability and maintainability but can dramatically cut down on the amount of code to write.

Note: Parameterless lambdas are only being used here because they are slightly more succinct than using anonymous methods.

Quick and (Very) Dirty Example

   1: (new Thread(() => {
   2:     DoLongRunningWork();
   3:     MessageBox.Show("Long Running Work Finished!");
   4: }) { Name = "Long Running Work Thread",
   5:     Priority = ThreadPriority.BelowNormal }).Start();


In only five lines of code, we create a new thread, give it some work to do (via parameterless lambda expression), assign the name and priority (thanks to object initializers) and then trigger it to start. This example assumes reasonable exception handling in the DoLongRunningWork() method and should be wrapped in a try…catch {} block to catch exceptions such as ThreadAbortException (See below for Exception Handling tips).


More Practical Example, Hide a Form for X Time

Taking advantage of closures (introduced in 2.0), we can build functions with arguments that are automatically available inside inline lambda statements. Have a look at this example, which hides a form for a certain timespan:


   1: private void MakeInvisible(TimeSpan Duration) {
   3:     (new System.Threading.Thread(() =>
   4:     {
   5:         try
   6:         {
   7:             this.Invoke(new MethodInvoker(this.Hide));
   8:             System.Threading.Thread.Sleep(Duration);
   9:         }
  10:         catch (ThreadAbortException ThreadAbort)
  11:         {
  12:             //...
  13:         }
  14:         catch (ThreadInterruptedException ThreadInterrupt)
  15:         {
  16:             //...
  17:         }
  18:         finally
  19:         {
  20:             this.Invoke(new MethodInvoker(this.Show));
  21:         }
  22:     })
  23:     {
  24:         Name = "MakeInvisible (" + Duration.ToString() + ")"
  25:     }
  26:     ).Start();
  28: }


Using the lambda statement, we are providing the set of instructions to run in our new thread and using property initializers towards the bottom of the function we are setting the thread name. Closures are used with the this keyword and Duration – both of which are passing through to the lambda statement automatically from the host method (MakeInvisible).

Although a bit poor on readability, I think this is a great example of how 3.0 and threading can replace a swath of other (much more complicated) code. To write the same example without the latest language features would take at least a timer and one more method, as well as additional code to wire everything together.


Use Airtight Exception Handling

In production applications, it pays you to have the most extensive error handling possible in all of your worker threads. Have a look at the following unsafe code example:

   1: private void UnsafeButton_Click(object sender, EventArgs e)
   2: {
   4:     int x = 0;
   5:     int y = 1 / x;
   7: }

If you were to start this application outside of the debugger and press the button, at worst you would receive the following dialog box:


Bad, but life goes on (via the Continue button). Now, if that same unsafe code is put in a worker thread:

   1: private void UnsafeButton_Click(object sender, EventArgs e)
   2: {
   3:     (new Thread(()=> {
   4:         int x = 0;
   5:         int y = 1 / x;
   6:     })).Start();
   7: }

You are asking for trouble. This is what happens:


The entire application shuts down without details about the exception or a way to continue. (This won’t happen inside the debugger, of course). An alternative to wrapping all of your worker threads in try…catch blocks is to handle the Application.ThreadException Event. This is a quicker way to ensure that your application won’t come to grinding halt when an exception is thrown in a worker thread.

As a final note, be aware that threads are susceptible to the special exceptions ThreadAbortException and ThreadInterruptedException. It’s possible to cancel an initiated abort by handling ThreadAbortException and calling ResetAbort() in the catch block.

Your email address will not be published. Required fields are marked *