Tutorial: Depuración de una aplicación paralela en Visual Studio (C#, Visual Basic y C++ )

En este tutorial se muestra cómo utilizar las ventanas Pilas paralelas y Tareas paralelas para depurar una aplicación paralela. Estas ventanas le ayudan a entender y comprobar el comportamiento del código en tiempo de ejecución que usa la biblioteca TPL o el 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

En este tutorial se da por supuesto que la opción Solo mi código está habilitada (está habilitada de forma predeterminada en las versiones más recientes de Visual Studio). En el menú Herramientas, seleccione 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 derecho 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 artículo. El código externo solo se aplica en el ejemplo de C#.

Ilustraciones

Las ilustraciones de este artículo se grabaron en un equipo básico de cuatro núcleos 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.

Creación del proyecto de ejemplo

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.

  1. Abra Visual Studio y cree un nuevo proyecto.

    Si la ventana de inicio no está abierta, elija Archivo>Ventana de inicio.

    En la ventana de inicio, elija Nuevo proyecto.

    En la ventana de inicio, elija Crear un proyecto nuevo.

    En el cuadro de búsqueda de la ventana Crear un proyecto, escriba consola. Seguidamente, elija C# , C++ o Visual Basic en la lista Lenguajes y, luego, Windows en la lista Plataformas.

    Después de aplicar los filtros de lenguaje y plataforma, elija Aplicación de consola para .NET Core o C++ y luego elija Siguiente.

    Nota

    Si no ve la plantilla correcta, vaya a Herramientas>Obtener herramientas y características... para abrir el Instalador de Visual Studio. Seleccione la carga de trabajo Desarrollo de escritorio de .NET o Desarrollo para el escritorio con C++ y, luego, elija Modificar.

    En la ventana Configurar el nuevo proyecto, escriba un nombre o use el nombre predeterminado en el cuadro Nombre del proyecto. A continuación, elija Siguiente o Crear, sea cual sea la opción que esté disponible.

    En .NET Core, elija la plataforma de destino recomendada o .NET 8; después, elija Crear.

    Aparecerá un nuevo proyecto de consola. Una vez que se ha creado el proyecto, aparece un archivo de código fuente.

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

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

    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();
    }
    

Después de actualizar el archivo de código, guarde los cambios y compile la solución.

  1. En el menú Archivo, seleccione Guardar todo.

  2. En el menú Compilar, seleccione Recompilar solución.

Observe que hay cuatro llamadas a Debugger.Break (DebugBreak en el ejemplo de C++). Por lo tanto, no es necesario insertar puntos de interrupción. La ejecución de la aplicación hace que se interrumpa en el depurador hasta cuatro veces.

Utilizar la Vista de subprocesos de la ventana Pilas paralelas

Para comenzar, en el menú Depurar, seleccione Iniciar depuración. Espere hasta que se alcance el primer punto de interrupción.

Ver la pila de llamadas de un único subproceso

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

  2. En el menú Depurar, seleccione Ventanas y, a continuación, seleccione Pila de llamadas. Acople la ventana Pila de llamadas a 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.

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.

Screenshot of Threads view in Parallel Stacks window.

Threads view in Parallel Stacks window

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 cuadro con el encabezado ([#] subprocesos). El subproceso actual se muestra en negrita.

Screenshot of Tooltip that shows thread IDs and names.

Tooltip that shows thread IDs and names

La flecha amarilla indica el marco de pila activo del subproceso actual.

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 derecho 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 Main en la ventana Subprocesos, observará que la flecha resaltada de la ventana Pilas paralelas se mueve de forma acorde.

Screenshot of Highlighted main thread in Parallel Stacks window.

Highlighted main thread in Parallel Stacks window

Reanudar ejecución hasta el segundo punto de interrupción

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

Screenshot of Parallel Stacks window that shows many branches.

Parallel Stacks window that shows many branches

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ó hasta el código externo de no usuario.

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.

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 entretejidas) 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 con un icono de flecha curvada.

Nota

Para una descripción de todos los iconos de la ventana Pilas paralelas, consulte Uso de la ventana Pilas paralelas.

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 derecho en S.J, seleccione Cambiar a marco y, a continuación, en un comando.

Screenshot of Parallel Stacks Path of Execution.

Parallel Stacks Path of Execution

Haga clic con el botón derecho en S.C y apunte 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 (solo se moverá la flecha curvada) o puede cambiar al otro subproceso (también se moverá el resaltado azul). La ilustración siguiente muestra los submenús.

Screenshot of Stacks menu with 2 options on C while J is current.

Stacks menu with 2 options on C while J is current

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.

En el caso de las 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. Para marcar subprocesos, utilice el menú contextual o la primera celda de un subproceso.

En la barra de herramientas, seleccione el botón Mostrar marcadas únicamente junto al cuadro de lista.

Screenshot of Parallel Stacks window and tooltip.

Parallel Stacks window and tooltip

Ahora solo el subproceso marcado se muestra en la ventana Pilas paralelas.

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, seleccione 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.

    Screenshot of Execution path in Parallel Stacks window.

    Execution path in Parallel Stacks window

    Observe que S.L está en negrita en los otros dos cuadros para que pueda ver dónde más aparece. Si quiere ver a qué marcos llamar en S.L y cuales son llamados, seleccione el botón Alternar vista de método en la barra de herramientas. En la siguiente ilustración se muestra la vista de método de la ventana Pilas paralelas.

    Screenshot of Method view in Parallel Stacks window.

    Method view in Parallel Stacks window

    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, respectivamente. Seleccione el botón Alternar vista de método de nuevo para salir de 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.

    • Configuración de símbolos abre los cuadros de diálogo respectivos.

    • Mostrar subprocesos en código fuente alterna la presentación de los marcadores de subproceso en el código fuente, que muestra la ubicación de los subprocesos en su código fuente.

    • 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 otros marcos (que pueden estar atenuados porque no tiene símbolos para ellos).

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

    Si tiene diagramas grandes y pasa al punto de interrupción siguiente, tal vez le interese que la vista se desplace de forma automática al marco de pila activo del subproceso actual, es decir, el subproceso que alcanzó primero el punto de interrupción.

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

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, seleccione 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 herramienta actual y vea la diferencia.

    La Vista aérea también permite ver diagramas grandes en la ventana Pilas paralelas. De forma predeterminada, la Vista aérea está activada. Pero puede desactivarla, haciendo 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.

    Screenshot of Birds eye view in Parallel Stacks window.

    Bird's-eye view in Parallel Stacks window

    En la vista aérea, 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 seleccionando 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. De manera alternativa, seleccione 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.

  2. Antes de continuar, en el menú Depurar, seleccione 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, seleccione Iniciar depuración y espere hasta alcanzar el primer punto de interrupción.

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

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

  4. Haga doble clic en un subproceso en 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, examinamos las tareas.

  5. En el menú Depurar, seleccione Ventanas y, a continuación, seleccione Subprocesos. En la siguiente ilustración se muestra la vista Tareas.

    Screenshot of Four running tasks in Tasks window.

    Four running tasks in Tasks window

    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, es decir, 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.

    Al cambiar manualmente de una tarea a otra, el contorno de la flecha indica el contexto del depurador actual para una tarea que no es la actual.

    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.

Reanudar ejecución hasta el segundo punto de interrupción

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

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

Screenshot of Two waiting tasks in Tasks window.

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

Two waiting tasks in Tasks window

La tarea 4, a su vez, está esperando a un monitor que pertenece al subproceso asignado a la tarea 2. (Haga clic con el botón derecho en la fila de encabezado y elija Columnas>Asignación de subproceso para ver el valor de asignación de subproceso de la tarea 2).

Waiting task and tooltip in Tasks window

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

Puede usar 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 usó 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.

Screenshot of Tasks view in Parallel Stacks window.

Tasks view in Parallel Stacks window

Los subprocesos que no están ejecutando tareas actualmente no se muestran en la opción 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.

Vea de nuevo la ventana Tareas. 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. Seleccione Parent. La columna Primario aparece sin valores para las cuatro tareas.

Reanudar la ejecución hasta el tercer punto de interrupción

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

Screenshot of Parent-child view in Tasks window.

En esta ejecución de ejemplo, observe que las tareas 11 y 12 se están ejecutando en el mismo subproceso (mostrar la columna Asignación de subprocesos si está oculta). Esta información no se muestra en la ventana Subprocesos. Poder verlo aquí es otra de las ventajas de la ventana Tareas. Para confirmar esto, vea la ventana Pilas paralelas. Asegúrese de que está viendo Tareas. Puede buscar las tareas 11 y 12 examinando la información sobre herramientas en la ventana Pilas paralelas.

Task view in Parallel Stacks window

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 derecho en la fila del encabezado de la columna y luego seleccione Vista de elemento primario y secundario. Vea la ilustración siguiente:

Parent-child view in Tasks window

Tenga en cuenta que las tareas 4 y 5 se están ejecutando en el mismo subproceso (mostrar la columna Asignación de subprocesos si está oculta). Esta información no se muestra en la ventana Subprocesos. Poder verlo aquí es otra de las ventajas de la ventana Tareas. 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. 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.

Task view in Parallel Stacks window

En la ventana Pilas paralelas, haga clic con el botón derecho en S.P y, a continuación, 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.

Highlighted thread in threads view

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

Reanudar la ejecución hasta el cuarto punto de interrupción

Para reanudar la ejecución hasta llegar al tercer punto de interrupción, seleccione Continuar en el menú Depurar. Seleccione el encabezado de columna Id. para ordenar por id. Vea la ilustración siguiente:

Screenshot of Four task states in Parallel Stacks window.

Las tareas 10 y 11 ahora se están esperando mutuamente y están bloqueadas. También hay varias tareas nuevas que ahora están programadas. Las tareas programadas son tareas que se han iniciado en código pero no se han ejecutado todavía. Por consiguiente, las columnas Ubicación y Asignación de subproceso muestran mensajes predeterminados o están vacías.

Four task states in Parallel Stacks window

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 bloqueadas. 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 Ubicación y Asignación de subproceso están vacías.

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

Header tooltip in Parallel Stacks window

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

Screenshot of Grouped tasks in Tasks window.

Grouped tasks in Tasks window

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.

La última característica para examinar de la ventana Tareas 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 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.