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
En el menú Archivo de Visual Studio, elija Nuevo y, a continuación, haga clic en Proyecto.
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.
Seleccione Aplicación de consola y, a continuación, haga clic en Aceptar. Mantenga la configuración Debug, que es el valor predeterminado.
Abra el archivo de código .cpp, .cs o .vb del proyecto. Elimine su contenido para crear un archivo de código vacío.
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;
}
En el menú Archivo, haga clic en Guardar todo.
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
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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
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.
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.
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.
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
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.
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.
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.
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
En el menú Depurar, haga clic en Iniciar depuración y espere hasta alcanzar el primer punto de interrupción.
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.
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.
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.
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.
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.
La tarea 4, a su vez, está esperando a un monitor que pertenece al subproceso asignado a la tarea 2.
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.
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.
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:
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.
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.
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:
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.
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.
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.
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.
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