The great difficulty in thread debugging is the lack of predictability and certainty in application behaviour. For a single-threaded application that is given the same application input, the behaviour will be the same for every execution, which makes tracking down unexpected behaviour quite easy. For a multi-threaded application, behaviour can be different even if user input is constant, because the order in which threads are scheduled for execution will vary each time the application is executed, potentially causing a much wider range of possible behaviours for the same input.
To make matters worse, some bugs appear only under a very small set of conditions, and these conditions can be difficult to predict and reproduce in a debugging session. In the multi-processor and multi-core world, using multiple threads in an application to take advantage of the available processing power is an important skill for a developer, and part of this skill is understanding how to work with threads in the debugger.
The work-horse for thread debugging is the Thread window. As shown in Figure 1, this window displays all the threads for an application, and includes the following fields:
- Flag state: Flags can be flagged within a debug session so that a thread that appears to be acting in unexpected ways can be monitored.
- Active Thread Column: The small second column in Figure 1 will display the call stack of the thread as a tooltip when the mouse is hovered over the column, as shown in Figure 2. It also will display whether the thread is the active thread in the debugger (represented by a yellow arrow) and whether it has been frozen (represented by a blue pause symbol).
- Thread Category: Threads are grouped into a number of categories. The thread containing the entry point of the application will be displayed as the Main Thread, other threads that have a message pump will be categorised as User Interface threads, and threads without message pumps will display as Worker Threads.
- Thread Name: The name of the thread will be the name of the thread type or entry point for native debugging and the name that the thread was given when it was created for managed debugging. The Thread Window context menu can be used to rename a thread. Naming a thread in native code can be accomplished by calling the RaiseException function from Kernel32.dll and passing a pointer to a THREADNAME_INFO variable as described in the MSDN documentation.
- Location: Shows the current function that the thread is executing. As with the Active Thread column, a tooltip will display the full thread call-stack when the mouse is hovered over the column.
- Priority: Shows the current priority assigned to the thread. This determines the amount of processing time that will be allocated to the thread for execution by the operating system.
- Suspend: Shows the suspend count. A thread must have a suspend count of zero to execute. The native debugger will not show an increment of the Suspend counter when the Freeze command is selected from the context menu, but with managed debugging, the Suspend count will reflect Freeze and Thaw commands.
Figure 1: The Thread Window
Figure 2: Thread Call Stack tooltip
For multi-threaded debugging, the Thread Window offers a great range of functionality to track down problems. The first part of an exercise is building a theory about which threads in an application are the ones causing a problem. This is particularly important in a server application that may have dozens of different threads running at the same time. Once a smaller sub-set of threads has been identified, the threads can be flagged using the Thread Window, and by selecting Show Only Flagged Threads from the Debug Location toolbar as shown in Figure 3, the Thread Windows can be de-cluttered by removing threads that aren't currently being investigated.
Figure 3: Debug Location Toolbar
One of the greatest challenges in multi-threaded debugging is gaining an overall view of what each thread is doing. Two Visual Studio 2008 enhancements go a significant way to addressing this problem—the tooltip call stack that is available from the Thread Window and shown in Figure 2 makes quickly inspecting what each thread is up to possible without the need to make every thread the active thread and inspecting the Call Stack window.
The second major enhancement is the Show Threads in Source option that can be activated via the Thread Window context menu or the Debug toolbar. When a breakpoint is hit, it often can be difficult to quickly determine which thread caused the breakpoint to be hit and whether other threads are currently executing the same statement or a statement close by. With the Show Threads in Source option enabled, a new icon will appear in the margin of the code window showing the lines of code that other threads are executing. In Figure 4, there are two other threads executing code in the same source code file, and hovering the mouse over the Thread icon will display the ID of the thread at that location.
Figure 4: Show Threads in Source