Tutorial: Depurar una aplicación paralela

En este tutorial se muestra cómo utilizar las ventanas Pilas paralelas y Tareas paralelas para depurar una aplicación paralela. Estas ventanas ayudan a entender y comprobar el comportamiento en tiempo de ejecución del código que utiliza Task Parallel Library o Runtime de simultaneidad. En este tutorial se proporciona código de muestra con puntos de interrupción integrados. Una vez que el código se interrumpe, se muestra cómo utilizar las ventanas Tareas paralelas y Pilas paralelas para examinarlo.

Se enseñan estas tareas:

  • Cómo ver las pilas de llamadas de todos los subprocesos en una vista.

  • Cómo ver la lista de instancias de System.Threading.Tasks.Task que se crean en la aplicación.

  • Cómo ver las pilas de llamadas reales de las tareas en lugar de los subprocesos.

  • Cómo navegar al código desde las ventanas Tareas paralelas y Pilas paralelas.

  • Cómo trabajan las ventanas con la escala de tamaño mediante la agrupación, el zoom y otras características relacionadas.

Requisitos previos

Debe tener Visual Studio 2010 instalado en el equipo.

En el tutorial se supone que Solo mi código está habilitado. En el menú Herramientas, haga clic en Opciones, expanda el nodo Depuración, seleccione General y, a continuación, seleccione Habilitar Solo mi código (solo administrado). Si no configura esta característica, puede utilizar este tutorial, pero los resultados pueden diferir de las ilustraciones.

Ejemplo de C#

Si utiliza el ejemplo de C#, también se supone que Código externo está oculto. Para alternar si se muestra el código externo, haga clic con el botón secundario en el encabezado de tabla Nombre de la ventana Pila de llamadas y, a continuación, active o desactive Mostrar código externo. Si no configura esta característica, puede utilizar este tutorial, pero los resultados pueden diferir de las ilustraciones.

Ejemplo de C++

Si utiliza el ejemplo C++, puede omitir las referencias a Código externo de este tema. El código externo solo se aplica en el ejemplo de C#.

Ilustraciones

Las ilustraciones de este tema se grabaron en un equipo básico quad core que ejecuta el ejemplo de C#. Aunque puede utilizar otras configuraciones para completar este tutorial, las ilustraciones pueden diferir de lo que se muestra en su equipo.

Crear el proyecto de muestra

El código de ejemplo de este tutorial es para una aplicación que no hace nada. El objetivo es entender cómo utilizar las ventanas de herramientas para depurar una aplicación paralela.

Para crear el proyecto de ejemplo

  1. En el menú Archivo de Visual Studio, elija Nuevo y, a continuación, haga clic en Proyecto.

  2. En el recuadro Plantillas instaladas, seleccione Visual C#, Visual Basic o Visual C++. En los lenguajes administrados, asegúrese de que aparece .NET Framework 4 en el cuadro del marco.

  3. Seleccione Aplicación de consola y, a continuación, haga clic en Aceptar. Mantenga la configuración Debug, que es el valor predeterminado.

  4. Abra el archivo de código .cpp, .cs o .vb del proyecto. Elimine su contenido para crear un archivo de código vacío.

  5. En el archivo de código vacío, pegue el siguiente código en el lenguaje elegido.

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
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;
}
  1. En el menú Archivo, haga clic en Guardar todo.

  2. En el menú Generar, haga clic en Volver a generar solución.

    Observe que hay cuatro llamadas a Debugger.Break (DebugBreak en el ejemplo de C++). Por tanto, no tiene que insertar puntos de interrupción; simplemente ejecutando la aplicación, el depurador se interrumpirá cuatro veces.

Utilizar la ventana Pilas paralelas: vista de subprocesos

En el menú Depurar, haga clic en Iniciar depuración. Espere a que se alcance el primer punto de interrupción.

Para ver la pila de llamadas de un subproceso

  1. En el menú Depurar, elija Ventanas y, a continuación, haga clic en Subprocesos. Acople la ventana Subprocesos en la parte inferior de Visual Studio.

  2. En el menú Depurar, seleccione Ventanas y, a continuación, haga clic en Pila de llamadas. Acople la ventana Pila de llamadas en la parte inferior de Visual Studio.

  3. Haga doble clic en un subproceso en la ventana Subprocesos para que sea el actual. Los subprocesos actuales tienen una flecha amarilla. Al cambiar el subproceso actual, la pila de llamadas se muestra en la ventana Pila de llamadas.

Para examinar la ventana Pilas paralelas

  • En el menú Depurar, seleccione Ventanas y, a continuación, haga clic en Pilas paralelas. Asegúrese de que Subprocesos está seleccionado en el cuadro de la esquina superior izquierda.

    Utilizando la ventana Pilas paralelas, puede ver al mismo tiempo varias pilas de llamadas en una vista. La siguiente ilustración muestra la ventana Pilas paralelas sobre la ventana Pila de llamadas.

    Ventana Pilas paralelas de la vista de subprocesos

    La pila de llamadas del subproceso principal aparece en un cuadro y las pilas de llamadas de los otros cuatro subprocesos están agrupadas en otro cuadro. Se agrupan cuatro subprocesos porque sus marcos de pila comparten los mismos contextos de método, es decir, están en los mismos métodos: A, B y C. Para ver los identificadores y los nombres de los subprocesos que comparten el mismo cuadro, desplace el puntero del mouse sobre el encabezado (4 Subprocesos). El subproceso actual se muestra en negrita, como en la siguiente ilustración.

    Información sobre herramientas con identificadores y nombres de subprocesos

    La flecha amarilla indica el marco de pila activo del subproceso actual. Para obtener más información, desplace el puntero del mouse sobre él

    Información sobre herramientas en marco de pila activo

    Puede establecer cuánto detalle mostrar en los marcos de pila (Nombres del módulo, Tipos de parámetros, Nombres de parámetros, Valores de parámetro, Números de línea y Desplazamientos de bytes) haciendo clic con el botón secundario del mouse en la ventana Pila de llamadas.

    Un resaltado azul alrededor de un cuadro indica que el subproceso actual forma parte de ese cuadro. El subproceso actual también está indicado por el marco de pila en negrita de la información sobre herramientas. Si hace doble clic en el subproceso principal en la ventana Subprocesos, puede observar que el resaltado azul de la ventana Pilas paralelas se mueve de forma acorde.

    Pilas con subproceso principal resaltado en azul

Para reanudar la ejecución hasta el segundo punto de interrupción

  • Para reanudar la ejecución hasta llegar al segundo punto de interrupción, haga clic en Continuar en el menú Depurar. La siguiente ilustración muestra el árbol de subproceso en el segundo punto de interrupción.

    Ventana Pilas paralelas con muchas bifurcaciones

    En el primer punto de interrupción, cuatro subprocesos fueron de S.A a S.B a los métodos S.C. Esa información todavía está visible en la ventana Pilas paralelas, pero los cuatro subprocesos han progresado más. Uno de ellos continuó a S.D y a S.E. Otro continuó hasta S.F, S.G y S.H. Los otros dos continuaron a S.I y S.J, y de allí uno fue a S.K y el otro continuó a código externo de no usuario.

    Puede desplazar el puntero del mouse sobre el encabezado del cuadro, por ejemplo, 1 subproceso o 2 subprocesos, para ver los identificadores de subproceso de los subprocesos. Puede desplazar el puntero del mouse sobre los marcos de pila para ver identificadores de subproceso y otros detalles del marco. El resaltado azul indica el subproceso actual y la flecha amarilla indica el marco de pila activo del subproceso actual.

    El icono de los subprocesos (las líneas onduladas azules y rojas superpuestas) indica los marcos de pila activos de los subprocesos que no son el actual. En la ventana Pila de llamadas, haga doble clic en S.B para intercambiar los marcos. La ventana Pilas paralelas indica el marco de pila del subproceso actual utilizando un icono de flecha verde y curvada.

    En la ventana Subprocesos, intercambie entre los subprocesos y observe que la vista en la ventana Pilas paralelas está actualizada.

    Puede cambiar a otro subproceso o al marco de otro subproceso, utilizando el menú contextual en la ventana Pilas paralelas. Por ejemplo, haga clic con el botón secundario en S.J, apunte a Cambiar a marco y, a continuación, haga clic en un comando.

    Ruta de acceso a pilas paralelas de ejecución

    Haga clic con el botón secundario en S.C y señale a Cambiar a marco. Uno de los comandos tiene una marca de verificación que indica el marco de pila del subproceso actual. Puede cambiar a ese marco del mismo subproceso (la flecha verde se moverá) o puede cambiar al otro subproceso (el resaltado azul también moverá). La ilustración siguiente muestra los submenús.

    Menú Pilas con 2 opciones en el marco del subproceso C mientras que el actual es el J

    Cuando un contexto de método está asociado solo a un marco de pila, el encabezado del cuadro muestra 1 subproceso y puede cambiar a él haciendo doble clic. Si hace doble clic en un contexto de método que tiene más que un marco asociado, el menú se abre automáticamente. Cuando desplace el puntero del mouse sobre los contextos de método, observe el triángulo negro a la derecha. Al hacer clic en ese triángulo, también se muestra el menú contextual.

    Con aplicaciones grandes que tienen muchos subprocesos, le interesa centrarse en un subconjunto de subprocesos. La ventana Pilas paralelas solo puede mostrar las pilas de llamadas de los subprocesos marcados. En la barra de herramientas, haga clic en el botón Mostrar marcadas únicamente junto al cuadro de lista.

    Ventana de pilas vacías e información sobre herramientas

    A continuación, en la ventana Subprocesos, marque los subprocesos uno por uno para ver cómo sus pilas de llamadas aparecen en la ventana Pilas paralelas. Para marcar subprocesos, utilice el menú contextual o la primera celda de un subproceso. Haga clic de nuevo en el botón de la barra de herramientas Mostrar marcadas únicamente para mostrar todos los subprocesos.

Para reanudar la ejecución hasta el tercer punto de interrupción

  1. Para reanudar la ejecución hasta llegar al tercer punto de interrupción, haga clic en Continuar en el menú Depurar.

    Cuando varios subprocesos están en el mismo método pero el método no estaba al principio de la pila de llamadas, el método aparece en cuadros diferentes. Un ejemplo en el punto de interrupción actual es S.L, que tiene tres subprocesos y aparece en tres cuadros. Haga doble clic en S.L.

    Ruta de acceso de ejecución de pilas paralelas

    Observe que S.L está en negrita en los otros dos cuadros para que pueda ver dónde más aparece. Si desea ver qué marcos llaman a S.L y a qué marcos llama, haga clic en el botón Alternar vista de método en la barra de herramientas. La siguiente ilustración muestra la vista de método de la ventana Pilas paralelas.

    Ventana Pilas en la vista de método

    Observe cómo el diagrama se monta en el método seleccionado y lo coloca en su propio cuadro en el medio de la vista. Los destinatarios y llamadores aparecen en la parte superior e inferior. Haga clic de nuevo en el botón Alternar vista de método para dejar este modo.

    El menú contextual de la ventana Pilas paralelas también tiene los siguientes otros elementos.

    • Presentación hexadecimal alterna los números de la información sobre herramientas entre decimal y hexadecimal.

    • Información de carga de símbolos y Configuración de símbolos abren los cuadros de diálogo respectivos.

    • Ir al código fuente e Ir al desensamblado navegan en el editor hasta el método seleccionado.

    • Mostrar código externo muestra todos los marcos aun cuando no estén en código de usuario. Pruébelo para ver el diagrama expandirse para alojar los marcos adicionales (que pueden estar atenuados porque no tiene símbolos para ellos).

    Si tiene diagramas grandes y pasa al punto de interrupción siguiente, tal vez le interese la vista para desplazarse de forma automática al marco de pila activo del subproceso actual, es decir, el subproceso que alcanzó primero el punto de interrupción. En la ventana Pilas paralelas asegúrese de que el botón Desplazar automáticamente a marco de pila actual de la barra de herramientas está activado.

    Desplazamiento automático en la ventana Pilas paralelas

  2. Antes de continuar, en la ventana Pilas paralelas, desplácese a la izquierda y hacia abajo todo lo posible.

Para reanudar la ejecución hasta el cuarto punto de interrupción

  1. Para reanudar la ejecución hasta llegar al cuarto punto de interrupción, haga clic en Continuar en el menú Depurar.

    Observe cómo la vista se desplaza automáticamente para ocupar su lugar. Alterne los subprocesos en la ventana Subprocesos o alterne los marcos de pila en la ventana Pila de llamadas y observe cómo la vista siempre se desplaza automáticamente hasta el marco correcto. Desactive la opción Desplazar automáticamente a marco de pila actual y vea la diferencia.

    La Vista aérea también permite ver diagramas grandes en la ventana Pilas paralelas. Para ver la Vista aérea, haga clic en el botón situado entre las barras de desplazamiento en la esquina inferior derecha de la ventana, como se muestra en la siguiente ilustración.

    Ventana Pilas paralelas con vista de pájaro

    Puede mover el rectángulo para hacer una rápida panorámica del diagrama.

    Otra manera de mover el diagrama en cualquier dirección es haciendo clic en un área en blanco del diagrama y arrastrando al lugar deseado.

    Para acercar y alejar, mantenga presionada la tecla CTRL mientras mueve la rueda del mouse. Alternativamente, haga clic en el botón Zoom en la barra de herramientas y utilice la herramienta Zoom.

    Pilas en paralelo ampliadas y reducidas

    También puede ver las pilas en dirección descendente en lugar de ascendente haciendo clic en el menú Herramientas, en Opciones y seleccionando o borrando la opción bajo el nodo Depuración.

  2. Antes de continuar, en el menú Depurar, haga clic en Detener depuración para finalizar la ejecución.

Utilizar la ventana Tareas paralelas y la vista Tareas de la ventana Pilas paralelas

Recomendamos completar los procedimientos anteriores antes de continuar.

Para reiniciar la aplicación hasta que alcance el primer punto de interrupción

  1. En el menú Depurar, haga clic en Iniciar depuración y espere hasta alcanzar el primer punto de interrupción.

  2. En el menú Depurar, elija Ventanas y, a continuación, haga clic en Subprocesos. Acople la ventana Subprocesos en la parte inferior de Visual Studio.

  3. En el menú Depurar, seleccione Ventanas y, a continuación, haga clic en Pila de llamadas. Acople la ventana Pila de llamadas en la parte inferior de Visual Studio.

  4. Haga doble clic en un subproceso de la ventana Subprocesos para que sea el actual. Los subprocesos actuales tienen la flecha amarilla. Al cambiar el subproceso actual, las otras ventanas se actualizan. A continuación, examinaremos las tareas.

  5. En el menú Depurar, apunte a Ventanas y, a continuación, haga clic en Tareas paralelas. La siguiente ilustración muestra la ventana Tareas paralelas.

    Ventana Tareas paralelas con 4 tareas en ejecución

    Por cada tarea que se esté ejecutando, puede leer su identificador, que devuelve la propiedad del mismo nombre, el identificador y nombre del subproceso que lo ejecuta, su ubicación (desplazando el puntero del mouse para mostrar una información sobre herramientas con la pila de llamadas completa). Asimismo, en la columna Tarea, puede ver el método que se pasó a la tarea; en otras palabras, el punto inicial.

    Puede ordenar cualquier columna. Observe el glifo de ordenación que indica la columna y la dirección de ordenación. También puede reordenar las columnas arrastrándolas a izquierda o derecha.

    La flecha amarilla indica la tarea actual. Puede intercambiar las tareas haciendo doble clic en una tarea o utilizando el menú contextual. Al intercambiar las tareas, el subproceso subyacente pasa a ser el actual y las demás ventanas se actualizan.

    Cuando pasa manualmente de una tarea a otra, la flecha amarilla se mueve, pero una flecha blanca sigue mostrando la tarea que hizo que el depurador se interrumpiera.

Para reanudar la ejecución hasta el segundo punto de interrupción

  • Para reanudar la ejecución hasta llegar al segundo punto de interrupción, haga clic en Continuar en el menú Depurar.

    Previamente, la columna Estado mostraba todas las tareas como en ejecución, pero ahora dos de las tareas están en espera. Las tareas se pueden bloquear por muchas razones diferentes. En la columna Estado, desplace el puntero del mouse sobre una tarea en espera para saber por qué está bloqueada. Por ejemplo, en la siguiente ilustración, la tarea 3 está esperando a la tarea 4.

    Ventana Tareas paralelas con 2 tareas en espera

    La tarea 4, a su vez, está esperando a un monitor que pertenece al subproceso asignado a la tarea 2.

    Ventana Tareas con información sobre herramientas y tarea en espera

    Puede marcar una tarea haciendo clic en la marca en la primera columna de la ventana Tareas paralelas.

    Puede utilizar las marcas para realizar el seguimiento de las tareas entre los puntos de interrupción diferentes en la misma sesión de depuración o filtrar por las tareas cuyas pilas de llamadas se muestren en la ventana Pilas paralelas.

    Cuando utilizó la ventana Pilas paralelas anteriormente, vio los subprocesos de la aplicación. Mire la ventana Pilas paralelas de nuevo, pero esta vez observe las tareas de la aplicación. Haga esto seleccionando Tareas en el cuadro de la izquierda superior. En la siguiente ilustración se muestra la vista Tareas.

    Ventana Tareas paralelas en la vista de tareas

    Los subprocesos que no están ejecutando tareas actualmente no se muestran en el vista de tareas de la ventana Pilas paralelas. Asimismo, con los subprocesos que ejecutan tareas, algunos de los marcos de pila que no son pertinentes para las tareas se filtran de la parte superior e inferior de la pila.

    Observe la ventana Tareas paralelas de nuevo. Haga clic con el botón secundario en cualquier encabezado de columna para ver un menú contextual de la columna.

    Menú de encabezado de columna de tareas paralelas

    Puede utilizar el menú contextual para agregar o quitar las columnas. Por ejemplo, la columna AppDomain no está seleccionada; por consiguiente, no se muestra en la lista. Haga clic en Primario. La columna Primario aparece sin valores para las cuatro tareas.

Para reanudar la ejecución hasta el tercer punto de interrupción

  • Para reanudar la ejecución hasta llegar al tercer punto de interrupción, haga clic en Continuar en el menú Depurar.

    Ahora se está ejecutando una nueva tarea, la tarea 5, y la tarea 4 está en espera. Puede ver por qué desplazando el puntero del mouse sobre la tarea en espera de la ventana Estado. En la columna Primario, observe que tarea 4 es el elemento primario de la tarea 5.

    Para visualizar la relación primario-secundario mejor, haga clic con el botón secundario en el encabezado de columna Primario y, a continuación, haga clic en Vista de elemento primario y secundario. Vea la ilustración siguiente:

    Vista de tareas paralelas en vista de elemento primario y secundario

    Observe que la tarea 4 y 5 se están ejecutando en el mismo subproceso. Esta información no se muestra en la ventana Subprocesos; poder verlo aquí es otra de las ventajas de la ventana Tareas paralelas. Para confirmar esto, vea la ventana Pilas paralelas. Asegúrese de que está viendo Tareas. Busque las tareas 4 y 5 haciendo doble clic en ellas en la ventana Tareas paralelas. Al hacerlo, se actualiza el resaltado azul de la ventana Pilas paralelas. También puede buscar las tareas 4 y 5 examinando la información sobre herramientas en la ventana Pilas paralelas.

    Ventana Pilas paralelas en la vista de tareas

    En la ventana Pilas paralelas, haga clic con el botón secundario en S.P y, a continuación, haga clic en Ir al subproceso. La ventana cambia a la vista de subprocesos y el marco correspondiente está a la vista. Puede ver ambas tareas en el mismo subproceso.

    Vista de subprocesos con subproceso resaltado

    Esta es otra ventaja de la vista de tareas de la ventana Pilas paralelas, comparada con la ventana Subprocesos.

Para reanudar la ejecución hasta el cuarto punto de interrupción

  • Para reanudar la ejecución hasta llegar al tercer punto de interrupción, haga clic en Continuar en el menú Depurar. Haga clic en el encabezado de columna Id. para ordenar por identificador. Vea la ilustración siguiente:

    Ventana Pilas paralelas con tareas en 4 estados

    Como la tarea 5 se ha completado, ya no se muestra. Si no es el caso en su equipo y no se muestra el interbloqueo, avance un paso presionando F11.

    Las tareas 3 y 4 se están esperando mutuamente y están interbloqueadas. Hay también 5 nuevas tareas que son elementos secundarios de la tarea 2 y se programan ahora. Las tareas programadas son tareas que se han iniciado en código pero no se han ejecutado todavía. Por consiguiente, las columnas Asignación de subproceso y Ubicación están vacías.

    Observe la ventana Pilas paralelas de nuevo. El encabezado de cada cuadro tiene una información sobre herramientas que muestra los identificadores y los nombres de los subprocesos. Cambie a la vista de tareas en la ventana Pilas paralelas. Desplace el puntero del mouse sobre un encabezado para ver el identificador, el nombre y el estado de la tarea, como se muestra en la siguiente ilustración.

    Ventana Pilas paralelas con información sobre herramientas de encabezado

    Puede agrupar las tareas por columna. En la ventana Tareas paralelas, haga clic con el botón secundario en el encabezado de columna Estado y, a continuación, haga clic en Agrupar por estado. La siguiente ilustración muestra la ventana Tareas paralelas agrupadas por estado.

    Ventana Tareas paralelas con tareas agrupadas

    También puede agrupar por cualquier otra columna. Agrupando las tareas, se puede concentrar en un subconjunto de tareas. Cada grupo contraíble tiene un recuento de los elementos que están agrupados. También puede marcar rápidamente todos los elementos del grupo haciendo clic en el botón Marcar a la derecho del botón Contraer.

    agrupado de Ventana Tareas paralelas

    La última característica para examinar de la ventana Tareas paralelas es el menú contextual que se muestra al hacer clic con el botón secundario en una tarea.

    expandido de menú contextual de la ventana Tareas paralelas

    El menú contextual muestra comandos diferentes, dependiendo del estado de la tarea. Los comandos pueden incluir Copiar, Seleccionar todo, Presentación hexadecimal, Cambiar a tarea, Inmovilizar subproceso asignado, Inmovilizar todos los subprocesos excepto este, Reanudar subproceso asignado y Marcar.

    Puede inmovilizar el subproceso subyacente de una o varias tareas, y puede inmovilizar todos los subprocesos exceptuando el asignado. Un subproceso inmovilizado se representa en la ventana Tareas paralelas como en la ventana Subprocesos, por un icono de pausa azul.

Resumen

En este tutorial se han mostrado las ventanas del depurador Tareas paralelas y Pilas paralelas. Utilice estas ventanas en los proyectos reales que utilizan código multithreading. Puede examinar código paralelo escrito en C++, C# o Visual Basic.

Vea también

Tareas

Tutorial: Depurar una aplicación paralela

Uso de la ventana Tareas paralelas

Uso de la ventaja Tareas paralelas

Conceptos

Programación paralela en .NET Framework

Runtime de simultaneidad

Otros recursos

Guía básica del depurador

Depurar código administrado