One of the clear differences between the release of Visual Studio 2010 (VS2010) and any of its predecessors is the new support for developing parallel code for multi-core computers. If you are a .NET developer, the 4.0 .NET Framework includes PLINQ and the Task Parallel Library (TPL). If you are a C++ developer, you can take advantage of the Parallel Patterns Library (PPL) and Asynchronous Agents. Regardless of your development language preferences, you will want to become acquainted with the tools and techniques that ease the analysis of parallel code execution.

The VS2010 debugger has received enhancements to better enable concurrency analysis scenarios.   Let’s take a look at a few of these enhancements and how to utilize them.

One of the most common debugger usage scenarios is Code Stepping.  When you are debugging under this scheme, you are able to examine the state of the program, variables, and related data before and after executing a particular line of code. This allows you to evaluate the effects of an instruction in isolation and to understand the behavior of the program.

You cannot access the Step commands while your application is running as they are only valid in "break" mode. After the after the debugger breaks into a process, all the threads in your application are halted. This halt in execution allows you to inspect the state of your application at a particular moment.

The most familiar debugger windows are the Threads window, the Call Stack window, and the variable windows (Locals, Autos, and Watch). The Threads window displays a list of all the threads in your process. The display per thread includes the thread ID and thread priority. The thread-list also displays an indication (a yellow arrow) of the current thread, which by default is the thread that was executing when the debugger broke into the process. The Call Stack window reveals the function calls that a specific thread was executing before reaching the current location.

The variable windows are used to inspect the values of variables in your application. The variables of local methods are usually browsed in the Locals and Autos windows; global state (variables not declared in a method) can be examined by adding them to the Watch window. You can also hover your mouse pointer over a variable of interest within the code editor window and view a small pop-up, balloon-type, window variable-specific information.

To examine variables that were in-scope earlier in the call stack of the thread, you need to change the current stack frame by double-clicking the stack frame history within the Call Stack window thus selecting a new "current stack frame". The current stack frame is indicated by a green curved tail arrow, while the active stack frame retains the yellow arrow.

A limitation of these classic debugger windows, specifically in debugging parallel applications, is that to obtain a complete understanding of how all of your threads are executing you will need to individually examine the calls stack of each thread by double-clicking each thread to make it current. Once you have done that, you must look at the Call Stack and variables windows attempting to derive a mental picture of your concurrent application. Consequently, two new debugger windows are now available to ease this burden, the Parallel Stacks and Parallel Tasks windows.

Parallel Stacks

The Parallel Stacks Threads View window shows call stack information for all the threads in your process. Access the Parallel Stacks window via the Debug menu, select Windows and then select Parallel Stacks.

Parallel Stacks lets you easily navigate between individual threads and their stack frames. The viewer coalesces, into the same node, the call stack segments that are common amongst threads at their root. The following graphic illustrates the Parallel Stacks window. You can thus see the call stacks of multiple threads in a single view. Parallel Stacks supports zoom, filtering of threads via flagging, and much of the functionality of the familiar Call Stack window.

clip_image001

The C++ Concurrency Runtime and the .NET4 enable a task-based programming model. If you are developing a task-based application, you can switch from the Threads View to Tasks View using the drop-down selection box in the upper left corner of the window.

clip_image002

From this perspective, call stacks of threads not participating as scheduled task execution units are omitted. Call stacks are trimmed to represent the real call stacks of tasks and not the thread-pool thread-context. A special feature of the Parallel Stacks window allows you to pivot the diagram on a single method and clearly observe the callers and callees of that method context.

Parallel Tasks

The Parallel Tasks window is very similar to the Threads window, except that it shows information about each System.Threading.Tasks.Task or task_handle object, instead of each thread. Like threads, tasks represent asynchronous operations that can run concurrently; however, multiple tasks may run on the same thread (a reuse of thread-pool execution units) for greater overall system efficiency. Access the Parallel Tasks window via the Debug menu, select Windows and then select Parallel Tasks.

This window displays a list of the running Tasks. You can read each task ID, the ID and name of the thread that runs it, and its stack location (hovering over an entry displays a tooltip illustrating the call stack). Also, under the Task column, you can see the method entry point of the Task.

ParallelTasksWindow

You can sort any column. You can also reorder the columns by dragging them left or right. The yellow arrow indicates the current task. You can switch tasks by double-clicking a task or by using the shortcut menu. When you switch tasks, the underlying thread becomes current and the other windows are updated. If you manually switch from one task to another, the yellow arrow moves, but a white arrow still shows the task that caused the debugger to break.

The Parallel Tasks window, exposes features similar to the Threads window, indicating the current task (the top-most task running on the current thread), the ability to switch the current task, flagging of tasks, and freezing and thawing threads.

One of the most valuable aids for developers is the Status column. The information provided in the Status column allows you to distinguish running tasks and tasks that are waiting on another task or on a synchronization primitive. It also helps you identify tasks that are deadlocked. Scheduled tasks are also displayed (tasks that have not run yet but are sitting in a scheduler queue waiting to be executed by a thread-pool thread).

An Example Scenario – Single Stepping with the Parallel Debugger

Here at Microsoft, we apparently receive a number of requests for leap-year solvers.   Consequently, it seems appropriate to offer this solution and concurrently leverage the debugger to identify and correct problems with our solution.   Our application is hard-coded to determine the leap-years between 1990 and 2190 and leverages 4 concurrent tasks partitioning the problem space by 4 concurrent 50 year partitions.   Unfortunately, our example has a problem.   When we run it, we see that it has missed identify the year 2000 as a leap-year and apparently misses every other year divisible by 400.   See the primary algorithm function below.   (Note:  This sample code is only for illustration of debugger features.  Please don’t use it otherwise or consider it as a reference of good coding practice or even a recommended way to utilize PPL tasks.)

CONST int YEARS_PER_TASK = 50;
CONST int BASE_YEAR = 1990;
 
static void listLeapYears(int Param)
{
  int taskNum = Param;
  long initialYear = BASE_YEAR + (taskNum * YEARS_PER_TASK);
  long year=0;
  std::cout << "Thread " <<  taskNum  << " start working." << std::endl;
 
 
  //Start from the initial date from which the thread should calculate leap years
  for(year=initialYear ; year<=initialYear+YEARS_PER_TASK ;year++) 
  {
    boolean isLeap = false;
 
    if(year % 4 == 0){
        if (year % 100 != 0){
            isLeap = true;
        }
    }
    Sleep(1);
    //There is an error in the isLeap function: "year % 400" was changed to "year / 400" in order to illustrate debugging
    if(year / 400 == 0){
        isLeap = true;
    }
 
    if(isLeap) std::cout << "Task " <<  taskNum <<  ") year: " << year << " ." << std::endl;
  }
}
 
 
Let’s set a breakpoint within the listLeapYears function and step-thru it in the debugger.
 
image 

Rebuild and begin execution of the application; wait for the stop to occur at the breakpoint.   Now, right-click over the taskNum variable and select Pin To Source.   Pin to Source is useful when you wish to inspect a variable in-situ.   This will leave a data-tip hanging-around in the code window showing you the current value of your variable while you continue step-wise debugging.

image

You can also pin a variable to the code window using a mouse-over technique.   Place your mouse over the year variable and then click the pin symbol.

image

Select Debug->Step Over or press F10 to advance execution to the next code line.   See the pin’ed variables updated within the code window.

image

Press F10 repeatedly to step through the code.   You will notice how other threads occasionally interrupt the execution of our step-wise debugging session.   This happens because other tasks are executing concurrently and their threads enter this code segment interleaved with the thread under our investigation.

Remove the breakpoint by clicking over the breakpoint indicator (red circle on the right edge of the code window).   Now, set a new breakpoint on the first if condition within the for loop (around line 59).

image

Right-click over the new breakpoint and select Condition…

image

On the breakpoint condition dialog window, type year == 2000.   Select OK and press F5 to restart your debugging session.  

image

After the new breakpoint is reached, select the menu option Debug->Windows->Parallel Stacks.

image

Ensure that the “Threads” view is selected as illustrated.

image

The Parallel Stacks window provides a consolidated view of what’s happening in our program.   You can see multiple call stacks concurrently.   If you hover your mouse over the header “4 Threads”, you’ll see the names and ID’s of the threads that share that specific call stack.   The current thread is displayed in bold.   The yellow arrow indicates the active stack frame.   Acquaint yourself with this window by exploring the various tool-bar options before moving-on.

Close the Parallel Stacks window and then select the menu option Debug->Windows->Parallel Tasks.

image

Note the resultant window pane.

image

We will take advantage of the debugger’s ability to “freeze” threads in order to single-step just the current thread.   This makes it easy to isolate a specific thread in a step-wise debugging session and avoid the interleaved context switching we experienced before.

Within the Parallel Tasks window, right-click on the Task which is marked with the yellow arrow (i.e. the current task).   From the popup menu, select Freeze All Threads but This.

image

This action will halt execution of all threads but the current thread.   Now, we can single-step through our code and analyze why our implementation fails to correctly identify the year 2000 as a leap-year.

Press F10 to step-over code statements until reaching the if (year / 400 == 0) conditional statement around line 66.   Notice how execution progressed into the sleep(1) statement and then control flow continued on the same task without jumping to another task.   This happens because the other tasks are frozen.

Press F10 further to observe the variable year (as 2000) and whether or not the conditional isLeap becomes true.   Notice that the isLeap = true; statement is skipped as we suspected.

image

It seems that our if statement logic is in error.   Instead of a division, “/”, we should have used a modulus, “%”, operation (meaning that the year is divisible by 400).   Let’s correct this code.

Select Debug->Stop Debugging from the menu or toolbar.   Change the line if (year / 400 == 0) to if (year % 400 == 0).  Press F5 to rebuild the application and to start debugging again.

image

Press F10 to continue single-stepping into the code.   Notice that now, the conditional statement is true and the year 2000 is correctly identified as a leap-year.

image

Press F5 to continue running the application and view the final output on the console window.   Note that years divisible by 400 are now correctly identified as leap years.

In summary, the new Parallel Stacks and Parallel Tasks features provide a more holistic perspective when visualizing the behavior of concurrent code.   The ability to freeze threads enables us to focus analysis on a specific thread’s execution and simplifies single-step debugging scenarios.   Capabilities such as pinning a variable display within the code window and setting conditional breakpoints provide powerful conveniences and productivity enhancements.  

Next, we’ll look at how VS2010 enhances our ability to analyze resource contention.