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
In an application with a large number of threads, having an excessive number of breakpoints hit can be a real problem during a debugging session. To overcome this problem, breakpoints can be filtered so that they are only active on particular threads. To filter a breakpoint, right-click on either the breakpoint icon in the code margin or the Breakpoint window and select Filter from the context menu. The dialog shown in Figure 5 will be displayed, and threads can be filtered based on either ID or name. Because the ID of a thread will change for each debug session, it will be more time efficient to include some debug-build only code that names the threads of interest so that a new filter condition does not need to be setup every time the debugged process is restarted.
Figure 5: Filter Breakpoint for Threads and Processes
Multi-threading debugging is one of the most challenging debugging experiences that a developer can undertake. Although nothing will replace experience and skill in working out why threads are interacting with each other in strange and unexpected ways, the Visual Studio 2008 debugger provides excellent support in controlling thread execution and inspecting what each thread in an application is doing. The new Show Threads in Source option is particularly useful for understanding the current execution paths of a source code file.
Multi-threading debugging is one of the most challenging debugging experiences that a developer can undertake. Even though nothing will replace experience and skill in working out why threads are interacting with each other in strange and unexpected ways, the Visual Studio 2008 debugger provides excellent support in controlling thread execution and inspecting what each thread in an application is doing. The new Show Threads in Source option is particularly useful for understanding the current execution paths of a source code file, and developers should ensure that it is enabled for all debugging sessions.
As with previous versions of Visual Studio, the Thread Window is the main focus of any multi-threading debugging session, allowing the active thread to be set, threads to be paused and thawed, and threads to be flagged for more detailed attention. New in Visual Studio 2008 is the call stack tooltip; it allows the tasks that each thread is performing to be quickly examined without changing the active thread.
About the Author
Nick Wienholt is an independent Windows and .NET consultant based in Sydney. He is the author of Maximizing .NET Performance and co-author of A Programmers Introduction to C# 2.0 from Apress, and specialises in system-level software architecture and development, with a particular focus of performance, security, interoperability, and debugging.
Nick is a keen and active participant in the .NET community. He is the co-founder of the Sydney Deep .NET User group and writes technical articles for Australian Developer Journal, ZDNet, Pinnacle Publishing, CodeGuru, MSDN Magazine (Australia and New Zealand Edition) and the Microsoft Developer Network. An archive of Nick's SDNUG presentations, articles, and .NET blog is available at www.dotnetperformance.com.
In recognition of his work in the .NET area, he was awarded the Microsoft Most Valued Professional Award from 2002 through 2007.