Walkthrough: Debugging a Parallel Application in Visual Studio

This walkthrough shows how to use the Parallel Tasks and Parallel Stacks windows to debug a parallel application. These windows help you understand and verify the runtime behavior of code that uses the Task Parallel Library (TPL) or the Concurrency Runtime. This walkthrough provides sample code that has built-in breakpoints. After the code breaks, the walkthrough shows how to use the Parallel Tasks and Parallel Stacks windows to examine it.

This walkthrough teaches these tasks:

  • How to view the call stacks of all threads in one view.

  • How to view the list of System.Threading.Tasks.Task instances that are created in your application.

  • How to view the real call stacks of tasks instead of threads.

  • How to navigate to code from the Parallel Tasks and Parallel Stacks windows.

  • How the windows cope with scale through grouping, zooming, and other related features.

Prerequisites

This walkthrough assumes that Just My Code is enabled (it is enabled by default in more recent versions of Visual Studio). On the Tools menu, click Options, expand the Debugging node, select General, and then select Enable Just My Code (Managed only). If you do not set this feature, you can still use this walkthrough, but your results may differ from the illustrations.

C# Sample

If you use the C# sample, this walkthrough also assumes that External Code is hidden. To toggle whether external code is displayed, right-click the Name table header of the Call Stack window, and then select or clear Show External Code. If you do not set this feature, you can still use this walkthrough, but your results may differ from the illustrations.

C++ Sample

If you use the C++ sample, you can ignore references to External Code in this topic. External Code only applies to the C# sample.

Illustrations

The illustrations in this topic recorded on a quad core computer running the C# sample. Although you can use other configurations to complete this walkthrough, the illustrations may differ from what is displayed on your computer.

Creating the Sample Project

The sample code in this walkthrough is for an application that does nothing. The goal is just to understand how to use the tool windows to debug a parallel application.

To create the sample project

  1. In Visual Studio, on the File menu, point to New and then click Project.

  2. In the Installed Templates pane, select either Visual C#, Visual Basic, or Visual C++. For the managed languages, ensure that .NET Framework 4 is displayed in the framework box.

  3. Select Console Application and then click OK. Remain in Debug configuration, which is the default.

  4. Open the .cpp, .cs, or .vb code file in the project. Delete its contents to create an empty code file.

  5. Paste the following code for your chosen language into the empty code file.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Diagnostics;
    
    class S
    {
      static void Main()
      {
        pcount = Environment.ProcessorCount;
        Console.WriteLine("Proc count = " + pcount);
        ThreadPool.SetMinThreads(4, -1);
        ThreadPool.SetMaxThreads(4, -1);
    
        t1 = new Task(A, 1);
        t2 = new Task(A, 2);
        t3 = new Task(A, 3);
        t4 = new Task(A, 4);
        Console.WriteLine("Starting t1 " + t1.Id.ToString());
        t1.Start();
        Console.WriteLine("Starting t2 " + t2.Id.ToString());
        t2.Start();
        Console.WriteLine("Starting t3 " + t3.Id.ToString());
        t3.Start();
        Console.WriteLine("Starting t4 " + t4.Id.ToString());
        t4.Start();
    
        Console.ReadLine();
      }
    
      static void A(object o)
      {
        B(o);
      }
      static void B(object o)
      {
        C(o);
      }
      static void C(object o)
      {
        int temp = (int)o;
    
        Interlocked.Increment(ref aa);
        while (aa < 4)
        {
          ;
        }
    
        if (temp == 1)
        {
          // BP1 - all tasks in C
          Debugger.Break();
          waitFor1 = false;
        }
        else
        {
          while (waitFor1)
          {
            ;
          }
        }
        switch (temp)
        {
          case 1:
            D(o);
            break;
          case 2:
            F(o);
            break;
          case 3:
          case 4:
            I(o);
            break;
          default:
            Debug.Assert(false, "fool");
            break;
        }
      }
      static void D(object o)
      {
        E(o);
      }
      static void E(object o)
      {
        // break here at the same time as H and K
        while (bb < 2)
        {
          ;
        }
        //BP2 - 1 in E, 2 in H, 3 in J, 4 in K
        Debugger.Break();
        Interlocked.Increment(ref bb);
    
        //after
        L(o);
      }
      static void F(object o)
      {
        G(o);
      }
      static void G(object o)
      {
        H(o);
      }
      static void H(object o)
      {
        // break here at the same time as E and K
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void I(object o)
      {
        J(o);
      }
      static void J(object o)
      {
        int temp2 = (int)o;
    
        switch (temp2)
        {
          case 3:
            t4.Wait();
            break;
          case 4:
            K(o);
            break;
          default:
            Debug.Assert(false, "fool2");
            break;
        }
      }
      static void K(object o)
      {
        // break here at the same time as E and H
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void L(object oo)
      {
        int temp3 = (int)oo;
    
        switch (temp3)
        {
          case 1:
            M(oo);
            break;
          case 2:
            N(oo);
            break;
          case 4:
            O(oo);
            break;
          default:
            Debug.Assert(false, "fool3");
            break;
        }
      }
      static void M(object o)
      {
        // breaks here at the same time as N and Q
        Interlocked.Increment(ref cc);
        while (cc < 3)
        {
          ;
        }
        //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
        Debugger.Break();
        Interlocked.Increment(ref cc);
        while (true)
          Thread.Sleep(500); // for ever
      }
      static void N(object o)
      {
        // breaks here at the same time as M and Q
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        R(o);
      }
      static void O(object o)
      {
        Task t5 = Task.Factory.StartNew(P, TaskCreationOptions.AttachedToParent);
        t5.Wait();
        R(o);
      }
      static void P()
      {
        Console.WriteLine("t5 runs " + Task.CurrentId.ToString());
        Q();
      }
      static void Q()
      {
        // breaks here at the same time as N and M
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        // task 5 dies here freeing task 4 (its parent)
        Console.WriteLine("t5 dies " + Task.CurrentId.ToString());
        waitFor5 = false;
      }
      static void R(object o)
      {
        if ((int)o == 2)
        {
          //wait for task5 to die
          while (waitFor5) { ;}
    
    
          int i;
          //spin up all procs
          for (i = 0; i < pcount - 4; i++)
          {
            Task t = Task.Factory.StartNew(() => { while (true);});
            Console.WriteLine("Started task " + t.Id.ToString());
          }
    
          Task.Factory.StartNew(T, i + 1 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 2 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 3 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 4 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent); //scheduled
    
          //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died
          Debugger.Break();
        }
        else
        {
          Debug.Assert((int)o == 4);
          t3.Wait();
        }
      }
      static void T(object o)
      {
        Console.WriteLine("Scheduled run " + Task.CurrentId.ToString());
      }
      static Task t1, t2, t3, t4;
      static int aa = 0;
      static int bb = 0;
      static int cc = 0;
      static bool waitFor1 = true;
      static bool waitFor5 = true;
      static int pcount;
      static S mylock = new S();
    }
    
    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    #include <ppl.h>
    #include <agents.h>
    #include <stdio.h>
    #include <concrtrm.h>
    #include <vector>
    
    CRITICAL_SECTION cs;
    
    using namespace ::std;
    using namespace ::std::tr1;
    using namespace ::Concurrency;
    task_group task4;
    task_group task3;
    task_group task2;
    
    volatile long aa = 0;
    volatile long bb = 0;
    volatile long cc = 0;
    static bool waitFor1 = true;
    static bool waitFor5 = true;
    
    #pragma optimize("", off)
    void Spin()
    {
       for(int i=0;i<50*50000;++i);
    }
    #pragma optimize("",on)
    
    template<class Func>
    class RunFunc
    {
       Func& m_Func;
       int m_o;
    public:
       RunFunc(Func func,int o):m_Func(func),m_o(o){
    
       };
       void operator()()const{
          m_Func(m_o);
       };
    };
    
    void T(int o)
    {
       cout << "Scheduled run \n";
    
    };
    
    void R(int o)
    {
       if (o == 2)
       {
          while (waitFor5) { ;}
          Spin();
    
          //use up all processors but 4 by scheduling 4 non-terminating tasks.
          int numProcsToBurn = GetProcessorCount() - 4;
          int i;
          vector<call<int>*> tasks;
          for (i = 0; i <  numProcsToBurn; i++)
          {
             tasks.push_back(new call<int>([](int i){while(true)Spin();}));
             asend(tasks[i],1);
             cout << "Started task  \n";
          }
    
          task_handle<RunFunc<decltype(T)>> t6(RunFunc<decltype(T)>(T,i + 1 + 5));
          task_handle<RunFunc<decltype(T)>> t7(RunFunc<decltype(T)>(T,i + 2 + 5));
          task_handle<RunFunc<decltype(T)>> t8(RunFunc<decltype(T)>(T,i + 3 + 5));
          task_handle<RunFunc<decltype(T)>> t9(RunFunc<decltype(T)>(T,i + 4 + 5));
          task_handle<RunFunc<decltype(T)>> t10(RunFunc<decltype(T)>(T,i + 5 + 5));
          task2.run(t6);
          task2.run(t7);
          task2.run(t8);
          task2.run(t9);
          task2.run(t10);
       
          //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died   
          DebugBreak();
       }
       else
       {
          if (o!=4)
             throw;
    
          task3.wait();      
       }
    };
    
    void Q()
    {
       // breaks here at the same time as N and M
       InterlockedIncrement(& cc);
       while (cc < 4)
       {
          ;
       }
       // task 5 dies here freeing task 4 (its parent)
       cout << "t5 dies\n";
       waitFor5 = false;
    };
    
    void P()
    {
       cout << "t5 runs\n";
       Q();
    };
    
    void O(int o)
    {   
       task_group t5;
       t5.run(&P);
       t5.wait();
       R(o);
    };
    
    void N(int o)
    {
       // breaks here at the same time as M and Q
       InterlockedIncrement(&cc);
       while (cc < 4)
       {
          ;
       }
       R(o);
    };
    
    void M(int o)
    {
       // breaks here at the same time as N and Q
       InterlockedIncrement(&cc);
       while (cc < 3)
       {
          ;
       }
       //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
       DebugBreak();
       InterlockedIncrement(&cc);
       while (true)
          Sleep(500); // for ever
    };
    
    void L(int oo)
    {
       int temp3 = oo;
    
       switch (temp3)
       {
       case 1:
          M(oo);
          break;
       case 2:
          N(oo);
          break;
       case 4:
          O(oo);
          break;
       default:
          throw; //fool3
          break;
       }
    }
    void K(int o)
    {
       // break here at the same time as E and H
       InterlockedIncrement(&bb);
       EnterCriticalSection(&cs);
       while (bb < 3)
       {
          ;
       }
       LeaveCriticalSection(&cs);
       Spin();
    
       //after
       L(o);
    }
    void J(int o)
    {
       int temp2 = o;
    
       switch (temp2)
       {
       case 3:
          task4.wait();
          break;
       case 4:
          K(o);
          break;
       default:
          throw; //fool2
          break;
       }
    }
    static void I(int o)
    {
       J(o);
    }
    static void H(int o)
    {
       // break here at the same time as E and K
       InterlockedIncrement(&bb);
       EnterCriticalSection(&cs);
       while (bb < 3)
       {
          ;
       }
       LeaveCriticalSection(&cs);
       Spin();
    
       //after
       L(o);
    }
    static void G(int o)
    {
       H(o);
    }
    static void F(int o)
    {
       G(o);
    }
    
    static void E(int o)
    {
       // break here at the same time as H and K
       while (bb < 2)
       {
          ;
       }
       //BP2 - 1 in E, 2 in H, 3 in J, 4 in K   
       Spin(); // for native case only
       DebugBreak();
       InterlockedIncrement(&bb);
    
       //after
       L(o);
    
    }
    
    static void D(int o)
    {
       E(o);
    }
    
    static void C(int o)
    {
       int temp = o;
    
       InterlockedIncrement(&aa);
       while (aa < 4)
       {
          ;
       }
    
       if (temp == 1)
       {
          // BP1 - all tasks in C   
          DebugBreak();
          waitFor1 = false;
       }
       else
       {
          while (waitFor1)
          {
             ;
          }
       }
       switch (temp)
       {
       case 1:
          D(o);
          break;
       case 2:
          F(o);
          break;
       case 3:
       case 4:
          I(o);
          break;
       default:
          throw; //fool
          break;
       }
    }
    static void B(int o)
    {
       C(o);
    }
    
    void A(int o)
    {
       B(o);
    }
    int main()
    {
       InitializeCriticalSection(&cs);
    
       task_group tasks;
       task_handle<RunFunc<decltype(A)>> t1(RunFunc<decltype(A)>(A,1));
       tasks.run(t1);
       task_handle<RunFunc<decltype(A)>> t2(RunFunc<decltype(A)>(A,2));
       task2.run(t2);
       task_handle<RunFunc<decltype(A)>> t3(RunFunc<decltype(A)>(A,3));
       task3.run(t3);
       task_handle<RunFunc<decltype(A)>> t4(RunFunc<decltype(A)>(A,4));
       task4.run(t4);
       
       getchar();
       return 1;
    }
    
    Imports System
    Imports System.Threading
    Imports System.Threading.Tasks
    Imports System.Diagnostics
    
    Module S
    
      Sub Main()
    
        pcount = Environment.ProcessorCount
        Console.WriteLine("Proc count = " + pcount.ToString())
        ThreadPool.SetMinThreads(4, -1)
        ThreadPool.SetMaxThreads(4, -1)
    
        t1 = New Task(AddressOf A, 1)
        t2 = New Task(AddressOf A, 2)
        t3 = New Task(AddressOf A, 3)
        t4 = New Task(AddressOf A, 4)
        Console.WriteLine("Starting t1 " + t1.Id.ToString())
        t1.Start()
        Console.WriteLine("Starting t2 " + t2.Id.ToString())
        t2.Start()
        Console.WriteLine("Starting t3 " + t3.Id.ToString())
        t3.Start()
        Console.WriteLine("Starting t4 " + t4.Id.ToString())
        t4.Start()
    
        Console.ReadLine()
      End Sub
      Sub A(ByVal o As Object)
        B(o)
      End Sub
      Sub B(ByVal o As Object)
        C(o)
      End Sub
      Sub C(ByVal o As Object)
    
        Dim temp As Integer = o
    
        Interlocked.Increment(aa)
        While (aa < 4)
        End While
    
        If (temp = 1) Then
          ' BP1 - all tasks in C
          Debugger.Break()
          waitFor1 = False
        Else
          While (waitFor1)
          End While
        End If
        Select Case temp
          Case 1
            D(o)
          Case 2
            F(o)
          Case 3, 4
            I(o)
          Case Else
            Debug.Assert(False, "fool")
        End Select
      End Sub
      Sub D(ByVal o As Object)
        E(o)
      End Sub
      Sub E(ByVal o As Object)
        ' break here at the same time as H and K
        While (bb < 2)
        End While
        'BP2 - 1 in E, 2 in H, 3 in J, 4 in K
        Debugger.Break()
        Interlocked.Increment(bb)
    
        'after
        L(o)
      End Sub
      Sub F(ByVal o As Object)
        G(o)
      End Sub
      Sub G(ByVal o As Object)
        H(o)
      End Sub
      Sub H(ByVal o As Object)
        ' break here at the same time as E and K
        Interlocked.Increment(bb)
        Monitor.Enter(mylock)
        While (bb < 3)
        End While
        Monitor.Exit(mylock)
    
        'after
        L(o)
      End Sub
      Sub I(ByVal o As Object)
        J(o)
      End Sub
      Sub J(ByVal o As Object)
    
        Dim temp2 As Integer = o
    
        Select Case temp2
          Case 3
            t4.Wait()
          Case 4
            K(o)
          Case Else
            Debug.Assert(False, "fool2")
        End Select
      End Sub
      Sub K(ByVal o As Object)
        ' break here at the same time as E and H
        Interlocked.Increment(bb)
        Monitor.Enter(mylock)
        While (bb < 3)
        End While
        Monitor.Exit(mylock)
    
        'after
        L(o)
      End Sub
      Sub L(ByVal oo As Object)
        Dim temp3 As Integer = oo
    
        Select Case temp3
          Case 1
            M(oo)
          Case 2
            N(oo)
          Case 4
            O(oo)
          Case Else
            Debug.Assert(False, "fool3")
        End Select
      End Sub
      Sub M(ByVal o As Object)
        ' breaks here at the same time as N and Q
        Interlocked.Increment(cc)
        While (cc < 3)
        End While
    
        'BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
        Debugger.Break()
        Interlocked.Increment(cc)
        While (True)
          Thread.Sleep(500) '  for ever
        End While
      End Sub
      Sub N(ByVal o As Object)
        ' breaks here at the same time as M and Q
        Interlocked.Increment(cc)
        While (cc < 4)
        End While
        R(o)
      End Sub
      Sub O(ByVal o As Object)
        Dim t5 As Task = Task.Factory.StartNew(AddressOf P, TaskCreationOptions.AttachedToParent)
        t5.Wait()
        R(o)
      End Sub
      Sub P()
        Console.WriteLine("t5 runs " + Task.CurrentId.ToString())
        Q()
      End Sub
      Sub Q()
        ' breaks here at the same time as N and M
        Interlocked.Increment(cc)
        While (cc < 4)
        End While
        ' task 5 dies here freeing task 4 (its parent)
        Console.WriteLine("t5 dies " + Task.CurrentId.ToString())
        waitFor5 = False
      End Sub
      Sub R(ByVal o As Object)
        If (o = 2) Then
          ' wait for task5 to die
          While waitFor5
          End While
    
          '//spin up all procs
          Dim i As Integer
          For i = 0 To pcount - 4 - 1
    
            Dim t As Task = Task.Factory.StartNew(Sub()
                                                    While True
    
                                                    End While
                                                  End Sub)
            Console.WriteLine("Started task " + t.Id.ToString())
          Next
    
          Task.Factory.StartNew(AddressOf T, i + 1 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
          Task.Factory.StartNew(AddressOf T, i + 2 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
          Task.Factory.StartNew(AddressOf T, i + 3 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
          Task.Factory.StartNew(AddressOf T, i + 4 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
          Task.Factory.StartNew(AddressOf T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent) ' //scheduled
    
          '//BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died
          Debugger.Break()
    
        Else
          Debug.Assert(o = 4)
          t3.Wait()
        End If
      End Sub
      Sub T(ByVal o As Object)
        Console.WriteLine("Scheduled run " + Task.CurrentId.ToString())
      End Sub
      Private t1, t2, t3, t4 As Task
      Private aa As Integer = 0
      Private bb As Integer = 0
      Private cc As Integer = 0
      Private waitFor1 As Boolean = True
      Private waitFor5 As Boolean = True
      Private pcount As Integer
      Private mylock As New S2()
    End Module
    
    Public Class S2
    
    End Class
    
  6. On the File menu, click Save All.

  7. On the Build menu, click Rebuild Solution.

    Notice that there are four calls to Debugger.Break (DebugBreak in the C++ sample) Therefore, you do not have to insert breakpoints; just running the application will cause it to break in the debugger up to four times.

Using the Parallel Stacks Window: Threads View

On the Debug menu, click Start Debugging. Wait for the first breakpoint to be hit.

To view the call stack of a single thread

  1. On the Debug menu, point to Windows and then click Threads. Dock the Threads window at the bottom of Visual Studio.

  2. On the Debug menu, point to Windows and then click Call Stack. Dock the Call Stack window at the bottom Visual Studio.

  3. Double-click a thread in the Threads window to make it current. Current threads have a yellow arrow. When you change the current thread, its call stack is displayed in the Call Stack window.

To examine the Parallel Stacks window

  1. On the Debug menu, point to Windows and then click Parallel Stacks. Make sure that Threads is selected in the box at the upper-left corner.

    By using the Parallel Stacks window, you can view multiple call stacks at the same time in one view. The following illustration shows the Parallel Stacks window above the Call Stack window.

    Threads view in Parallel Stacks window

    The call stack of the Main thread appears in one box and the call stacks for the other four threads are grouped in another box. Four threads are grouped together because their stack frames share the same method contexts; that is, they are in the same methods: A, B, and C. To view the thread IDs and names of the threads that share the same box, hover over the box with the header (4 Threads). The current thread is displayed in bold.

    Tooltip that shows thread IDs and names

    The yellow arrow indicates the active stack frame of the current thread.

    You can set how much detail to show for the stack frames (Module Names, Parameter Types, Parameter Names, Parameter Values, Line Numbers and Byte Offsets) by right-clicking in the Call Stack window.

    A blue highlight around a box indicates that the current thread is part of that box. The current thread is also indicated by the bold stack frame in the tooltip. If you double-click the Main thread in the Threads window, you can observe that the blue highlight in the Parallel Stacks window moves accordingly.

    Highlighted main thread in Parallel Stacks window

To resume execution until the second breakpoint

  1. To resume execution until the second breakpoint is hit, on the Debug menu, click Continue. The following illustration shows the thread tree at the second breakpoint.

    Parallel Stacks window that shows many branches

    At the first breakpoint, four threads all went from S.A to S.B to S.C methods. That information is still visible in the Parallel Stacks window, but the four threads have progressed further. One of them continued to S.D and then S.E. Another continued to S.F, S.G, and S.H. Two others continued to S.I and S.J, and from there one of them went to S.K and the other continued to non-user External Code.

    You can hover over the box header, for example, 1 Thread or 2 Threads, to see the thread IDs of the threads. You can hover over stack frames to see thread IDs plus other frame details. The blue highlight indicates the current thread and the yellow arrow indicates the active stack frame of the current thread.

    The cloth-threads icon (interweaved lines) indicate the active stack frames of the noncurrent threads. In the Call Stack window, double-click S.B to switch frames. The Parallel Stacks window indicates the current stack frame of the current thread by using a green curved arrow icon.

    In the Threads window, switch between threads and observe that the view in the Parallel Stacks window is updated.

    You can switch to another thread, or to another frame of another thread, by using the shortcut menu in the Parallel Stacks window. For example, right-click S.J, point to Switch To Frame, and then click a command.

    Parallel Stacks Path of Execution

    Right-click S.C and point to Switch To Frame. One of the commands has a check mark that indicates the stack frame of the current thread. You can switch to that frame of the same thread (just the green arrow will move) or you can switch to the other thread (the blue highlight will also move). The following illustration shows the submenu.

    Stacks menu with 2 options on C while J is current

    When a method context is associated with just one stack frame, the box header displays 1 Thread and you can switch to it by double-clicking. If you double-click a method context that has more than 1 frame associated with it, then the menu automatically pops up. As you hover over the method contexts, notice the black triangle at the right. Clicking that triangle also displays the shortcut menu.

    For large applications that have many threads, you may want to focus on just a subset of threads. The Parallel Stacks window can display call stacks only for flagged threads. To flag threads, use the shortcut menu or the first cell of a thread.

    On the toolbar, click the Show Only Flagged button next to the list box.

    Parallel Stacks window and tooltip

    Now, only the flagged thread shows up in the Parallel Stacks window.

To resume execution until the third breakpoint

  1. To resume execution until the third breakpoint is hit, on the Debug menu, click Continue.

    When multiple threads are in the same method but the method was not at the beginning of the call stack, the method appears in different boxes. An example at the current breakpoint is S.L, which has three threads in it and appears in three boxes. Double-click S.L.

    Execution path in Parallel Stacks window

    Notice that S.L is bold in the other two boxes so that you can see where else it appears. If you want to see which frames call into S.L and which frames it calls, click the Toggle Method View button on the toolbar. The following illustration shows the method view of The Parallel Stacks window.

    Method view in Parallel Stacks window

    Notice how the diagram pivoted on the selected method and positioned it in its own box in the middle of the view. The callees and callers appear on the top and bottom. Click the Toggle Method View button again to leave this mode.

    The shortcut menu of the Parallel Stacks window also has the following other items.

    • Hexadecimal Display toggles the numbers in the tooltips between decimal and hexadecimal.

    • Symbol Settings open the respective dialog boxes.

    • Show Threads in Source toggles the display of thread markers in your source code, which shows the location of threads in your source code.

    • Show External Code displays all the frames even if they are not in user code. Try it to see the diagram expand to accommodate the additional frames (which may be dimmed because you do not have symbols for them).

  2. In the Parallel Stacks window, make sure that the Auto Scroll to Current Stack Frame button on the toolbar is on.

    When you have large diagrams and you step to the next breakpoint, you may want the view to auto scroll to the active stack frame of the current thread; that is, the thread that hit the breakpoint first.

  3. Before you continue, in the Parallel Stacks window, scroll all the way to the left and all the way down.

To resume execution until the fourth breakpoint

  1. To resume execution until the fourth breakpoint is hit, on the Debug menu, click Continue.

    Notice how the view autoscrolled into place. Switch threads in the Threads window or switch stack frames in the Call Stack window and notice how the view always autoscrolls to the correct frame. Turn off Auto Scroll to Current Tool Frame option and view the difference.

    The Bird's Eye View also helps with large diagrams in the Parallel Stacks window. By default, the Bird's Eye View is on. But you can toggle it by clicking the button between the scroll bars on the lower-right corner of the window, as shown in the following illustration.

    Bird's-eye view in Parallel Stacks window

    In bird's eye view, you can move the rectangle to quickly pan around the diagram.

    Another way to move the diagram in any direction is to click a blank area of the diagram and drag it where you want it.

    To zoom in and out of the diagram, press and hold CTRL while you move the mouse wheel. Alternatively, click the Zoom button on the toolbar and then use the Zoom tool.

    You can also view the stacks in a top-down direction instead of bottom-up, by clicking the Tools menu, clicking Options, and then select or clear the option under the Debugging node.

  2. Before you continue, on the Debug menu, click Stop Debugging to end execution.

Using the Parallel Tasks Window and the Tasks View of the Parallel Stacks window

We recommended that you complete the earlier procedures before you continue.

To restart the application until the first breakpoint is hit

  1. On the Debug menu, click Start Debugging and wait for the first breakpoint to be hit.

  2. On the Debug menu, point to Windows and then click Threads. Dock the Threads window at the bottom of Visual Studio.

  3. On the Debug menu, point to Windows and click Call Stack. Dock the Call Stack window at the bottom Visual Studio.

  4. Double-click a thread in the Threads window to makes it current. Current threads have the yellow arrow. When you change the current thread, the other windows are updated. Next, we will examine tasks.

  5. On the Debug menu, point to Windows, and then click Tasks. The following illustration shows the Tasks window.

    Four running tasks in Tasks window

    For each running Task, you can read its ID, which is returned by the same-named property, the ID and name of the thread that runs it, its location (hovering over that displays a tooltip that has the whole call stack). Also, under the Task column, you can see the method that was passed into the task; in other words, the starting point.

    You can sort any column. Notice the sort glyph that indicates the sort column and direction. 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.

    When 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.

To resume execution until the second breakpoint

  1. To resume execution until the second breakpoint is hit, on the Debug menu, click Continue.

    Previously, the Status column showed all tasks as Active, but now two of the tasks are Blocked. Tasks can be blocked for many different reasons. In the Status column, hover over a waiting task to learn why it is blocked. For example, in the following illustration, task 3 is waiting on task 4.

    Two waiting tasks in Tasks window

    Task 4, in turn, is waiting on a monitor owned by the thread assigned to task 2. (Right-click the header row and choose Columns > Thread Assignment to view the thread assignment value for task 2).

    Waiting task and tooltip in Tasks window

    You can flag a task by clicking the flag in the first column of the Tasks window.

    You can use flagging to track tasks between different breakpoints in the same debugging session or to filter for tasks whose call stacks are shown in the Parallel Stacks window.

    When you used the Parallel Stacks window earlier, you viewed the application threads. View the Parallel Stacks window again, but this time view the application tasks. Do this by selecting Tasks in the box on the upper left. The following illustration shows the Tasks View.

    Tasks view in Parallel Stacks window

    Threads that are not currently executing tasks are not shown in the Tasks View of the Parallel Stacks window. Also, for threads that execute tasks, some of the stack frames that are not relevant to tasks are filtered from the top and bottom of the stack.

    View the Tasks window again. Right-click any column header to see a shortcut menu for the column.

    You can use the shortcut menu to add or remove columns. For example, the AppDomain column is not selected; therefore, it is not displayed in the list. Click Parent. The Parent column appears without values for any of the four tasks.

To resume execution until the third breakpoint

  1. To resume execution until the third breakpoint is hit, on the Debug menu, click Continue.

    A new task, task 5, is now running and task 4 is now waiting. You can see why by hovering over the waiting task in the Status window. In the Parent column, notice that task 4 is the parent of task 5.

    To better visualize the parent-child relationship, right-click the column header row and then click Parent Child View. You should see the following illustration.

    Parent-child view in Tasks window

    Notice that task 4 and task 5 are running on the same thread (show the Thread Assignment column if it is hidden). This information is not displayed in the Threads window; seeing it here is another benefit of the Tasks window. To confirm this, view the Parallel Stacks window. Make sure that you are viewing Tasks. Locate tasks 4 and 5 by double-clicking them in the Tasks window. When you do, the blue highlight in the Parallel Stacks window is updated. You can also locate tasks 4 and 5 by scanning the tooltips on the Parallel Stacks window.

    Task view in Parallel Stacks window

    In the Parallel Stacks window, right-click S.P, and then click Go To Thread. The window switches to Threads View and the corresponding frame is in view. You can see both tasks on the same thread.

    Highlighted thread in threads view

    This is another benefit of the Tasks View in the Parallel Stacks window, compared to the Threads window.

To resume execution until the fourth breakpoint

  1. To resume execution until the third breakpoint is hit, on the Debug menu, click Continue. Click the ID column header to sort by ID. You should see the following illustration.

    Four task states in Parallel Stacks window

    Because task 5 has completed, it is no longer displayed. If that is not the case on your computer and the deadlock is not shown, step one time by pressing F11.

    Task 3 and task 4 are now waiting on each other and are blocked. There are also 5 new tasks that are children of task 2 and are now scheduled. Scheduled tasks are tasks that have been started in code but have not run yet. Therefore, their Location and Thread Assignment columns are empty.

    View the Parallel Stacks window again. The header of each box has a tooltip that shows the thread IDs and names. Switch to Tasks View in the Parallel Stacks window. Hover over a header to see the task ID and name, and the status of the task, as shown in the following illustration.

    Header tooltip in Parallel Stacks window

    You can group the tasks by column. In the Tasks window, right-click the Status column header and then click Group by Status. The following illustration shows the Tasks window grouped by status.

    Grouped tasks in Tasks window

    You can also group by any other column. By grouping tasks, you can focus on a subset of tasks. Each collapsible group has a count of the items that are grouped together.

    The last feature of the Tasks window to examine is the shortcut menu that is displayed when you right-click a task.

    The shortcut menu displays different commands, depending on the status of the task. The commands may include Copy, Select All, Hexadecimal Display, Switch to Task, Freeze Assigned Thread, Freeze All Threads But This, and Thaw Assigned Thread, and Flag.

    You can freeze the underlying thread of a task, or tasks, or you can freeze all threads except the assigned one. A frozen thread is represented in the Tasks window as it is in the Threads window, by a blue pause icon.

Summary

This walkthrough demonstrated the Parallel Tasks and Parallel Stacks debugger windows. Use these windows on real projects that use multithreaded code. You can examine parallel code written in C++, C#, or Visual Basic.

See Also

Debugging Multithreaded Applications
Debugger Basics
Debugging Managed Code
Parallel Programming
Concurrency Runtime
Using the Parallel Stacks Window
Using the Tasks Window