Introducción a la depuración de aplicaciones multiproceso (C#, Visual Basic, C++)

Visual Studio proporciona varias herramientas y elementos de la interfaz de usuario para ayudarle a depurar aplicaciones multiproceso. En este tutorial se muestra cómo usar marcadores de subprocesos, la ventanas Pilas paralelas e Inspección paralela, y los puntos de interrupción condicionales y de filtro. Al finalizar este tutorial se habrá familiarizado con las características de Visual Studio para la depuración de aplicaciones multiproceso.

En estos dos artículos se proporciona información adicional sobre cómo usar otras herramientas de depuración multiproceso:

El primer paso es crear un proyecto de aplicación multiproceso.

Creación de un proyecto de aplicación multiproceso

  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 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 la plantilla Aplicación de consola para .NET 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 MyThreadWalkthroughApp en el cuadro Nombre del proyecto. A continuación, elija Siguiente o Crear, sea cual sea la opción que esté disponible.

    Para un proyecto de .NET Core o .NET 5+, 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. En función del lenguaje elegido, el archivo de código fuente se podría denominar Program.cs, MyThreadWalkthroughApp.cpp o Module1.vb.

  2. Elimine el código que aparece en el archivo de origen y reemplácelo por el siguiente código actualizado. Elija el fragmento de código adecuado para la configuración del código.

    using System;
    using System.Threading;
    
    public class ServerClass
    {
    
        static int count = 0;
        // The method that will be called when the thread is started.
        public void InstanceMethod()
        {
            Console.WriteLine(
                "ServerClass.InstanceMethod is running on another thread.");
    
            int data = count++;
            // Pause for a moment to provide a delay to make
            // threads more apparent.
            Thread.Sleep(3000);
            Console.WriteLine(
                "The instance method called by the worker thread has ended. " + data);
        }
    }
    
    public class Simple
    {
        public static void Main()
        {
            for (int i = 0; i < 10; i++)
            {
                CreateThreads();
            }
        }
        public static void CreateThreads()
        {
            ServerClass serverObject = new ServerClass();
    
            Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod));
            // Start the thread.
            InstanceCaller.Start();
    
            Console.WriteLine("The Main() thread calls this after "
                + "starting the new InstanceCaller thread.");
    
        }
    }
    
  3. En el menú Archivo, seleccione Guardar todo.

  4. (Solo para Visual Basic) En el Explorador de soluciones (panel de la derecha), haga clic con el botón derecho en el nodo del proyecto y seleccione Propiedades. En la pestaña Aplicación, cambie Objeto de inicio a Simple.

Depuración de la aplicación multiproceso

  1. En el editor de código fuente, busque el fragmento de código siguiente:

    Thread.Sleep(3000);
    Console.WriteLine();
    
  2. Haga clic en el margen izquierdo de la instrucción Thread.Sleep o, en C++, std::this_thread::sleep_for, para insertar un nuevo punto de interrupción.

    En el margen, un círculo de color rojo indica que se ha establecido un punto de interrupción en esa ubicación.

  3. En el menú Depurar, seleccione Iniciar depuración (F5).

    Visual Studio compila la solución, la aplicación comienza a ejecutarse con el depurador adjunto y, después, se detiene en el punto de interrupción.

  4. En el editor de código fuente, busque la línea que contiene el punto de interrupción.

Detección del marcador de subproceso

  1. En la barra de herramientas de depuración, seleccione el botón Mostrar subprocesos en código fuenteShow Threads in Source.

  2. Presione F11 dos veces para avanzar el depurador.

  3. Examine el margen interno izquierdo de la ventana. En esta línea verá un icono de marcador de subprocesoThread Marker que se parece a dos hilos entrelazados. El marcador de subproceso indica que un subproceso se ha detenido en esa ubicación.

    Un punto de interrupción puede ocultar parcialmente un marcador de subproceso.

  4. Desplace el puntero sobre el marcador de subproceso. Aparece una sugerencia de datos en la que se indican el nombre y el número de id. de subproceso de cada subproceso detenido. En este caso, el nombre probablemente sea <noname>.

    Screenshot of the Thread ID in a DataTip.

  5. Seleccione el marcador de subproceso para ver las opciones disponibles en el menú contextual.

Visualización de ubicaciones de subprocesos

En la ventana Pilas paralelas, puede cambiar entre una vista Subprocesos y (para la programación basada en tareas) la vista Tareas, y ver la información de la pila de llamadas de cada subproceso. En esta aplicación, se puede usar la vista Subprocesos.

  1. Para abrir la ventana Pilas paralelas, seleccione Depurar>Ventanas>Pilas paralelas. Debería ver algo parecido a lo siguiente. La información exacta puede variar en función de la ubicación actual de cada subproceso, el hardware y el lenguaje de programación.

    Screenshot of the Parallel Stacks Window.

    En este ejemplo, de izquierda a derecha, se ve esta información para código administrado:

    • El subproceso actual (flecha amarilla) ha entrado en ServerClass.InstanceMethod. Para ver el id. de subproceso y el marco de pila de un subproceso, mantenga el puntero sobre ServerClass.InstanceMethod.
    • El subproceso 31724 está esperando un bloqueo propiedad del subproceso 20272.
    • El subproceso principal (lado izquierdo) se ha detenido en [Código externo], que puede ver con detalle si elige Mostrar código externo.

    Parallel Stacks Window

    En este ejemplo, de izquierda a derecha, se ve esta información para código administrado:

    • El subproceso principal (lado izquierdo) se ha detenido en Thread.Start, donde el punto de detención se identifica mediante el icono de marcador de subproceso Thread Marker.
    • Dos subprocesos han entrado en ServerClass.InstanceMethod; uno de ellos es el subproceso actual (flecha amarilla), mientras que el otro se ha detenido en Thread.Sleep.
    • También se inicia un nuevo subproceso (a la derecha), pero se detiene en ThreadHelper.ThreadStart.
  2. Para ver los subprocesos en una vista de lista, seleccione Depurar>Windows>Subprocesos.

    Screenshot of the Threads Window.

    En esta vista, puede ver fácilmente que el subproceso 20272 es el subproceso principal y se encuentra actualmente en código externo, específicamente System.Console.dll.

    Nota:

    Para obtener más información sobre el uso de la ventana Subprocesos, vea Tutorial: Depuración de una aplicación multiproceso.

  3. Haga clic con el botón derecho en las entradas de la ventana Pilas paralelas o Subprocesos para ver las opciones disponibles en el menú contextual.

    Puede realizar varias acciones en estos menús con el botón derecho. Para este tutorial, explorará más detalles en la ventana Inspección paralela (secciones siguientes).

Establecimiento de una inspección en una variable

  1. Para abrir la ventana Inspección paralela, seleccione Depurar>Ventanas>Inspección paralela>Inspección paralela 1.

  2. Seleccione la celda en la que se ve el texto <Add Watch> (o la celda de encabezado vacía en la cuarta columna) y escriba data.

    En la ventana aparecerán los valores de la variable de datos para cada subproceso.

  3. Seleccione la celda en la que se ve el texto <Add Watch> (o la celda de encabezado vacía en la quinta columna) y escriba count.

    En la ventana aparecerán los valores de la variable count de datos para cada subproceso. Si todavía no ve mucha información, intente presionar F11 varias veces para avanzar la ejecución de los subprocesos en el depurador.

    Parallel Watch Window

  4. Haga clic con el botón derecho en una de las filas de la ventana para ver las opciones disponibles.

Marcar y desmarcar subprocesos

Puede marcar los subprocesos para realizar el seguimiento de los importantes y omitir el resto.

  1. En la ventana Inspección paralela, mantenga presionada la tecla Mayús y seleccione varias filas.

  2. Haga clic con el botón derecho y seleccione Marcar.

    Se marcarán todos los subprocesos seleccionados. Ahora, puede filtrar para mostrar solo los subprocesos marcados.

  3. En la ventana Inspección paralela, seleccione el botón Mostrar solo subprocesos marcadosShow Flagged Threads.

    En la lista solo aparecerán los subprocesos marcados.

    Sugerencia

    Después de haber marcado algunos subprocesos, puede hacer clic con el botón derecho en una línea de código en el editor de código y elegir Ejecutar subprocesos marcados hasta el cursor. Asegúrese de elegir código al que accedan todos los subprocesos marcados. Visual Studio pausará los subprocesos en la línea de código seleccionada, lo que facilita el control del orden de ejecución mediante la inmovilización y reanudación de los subprocesos.

  4. Vuelva a seleccionar el botón Mostrar solo subprocesos marcados para activar de nuevo el modo Mostrar todos los subprocesos.

  5. Para quitar los marcadores de subprocesos, haga clic con el botón derecho en uno o más subprocesos marcados en la ventana Inspección paralela y seleccione Quitar marca.

Inmovilización y reanudación de la ejecución de subprocesos

Sugerencia

Puede inmovilizar y reanudar los subprocesos (suspenderlos y reanudarlos) para controlar el orden en el que realizan el trabajo. Esto puede ayudarle a resolver incidencias de simultaneidad como interbloqueos y condiciones de carrera.

  1. En la ventana Inspección paralela, con todas las filas seleccionadas, haga clic con el botón derecho y seleccione Inmovilizar.

    En la segunda columna, aparece un icono de pausa para cada fila. El icono de pausa indica que el subproceso está inmovilizado.

  2. Para anular la selección de todas las filas restantes, seleccione solo una.

  3. Haga clic con el botón derecho en una fila y seleccione Reanudar.

    El icono de pausa desaparece de esta fila, lo que indica que el subproceso ya no está inmovilizado.

  4. Cambie al editor de código y presione F11. Solo se ejecuta el subproceso inmovilizado.

    La aplicación también puede crear instancias de algunos subprocesos nuevos. Los subprocesos nuevos no están marcados ni se inmovilizan.

Seguimiento de un solo subproceso con puntos de interrupción condicionales

Puede ser útil seguir la ejecución de un único subproceso en el depurador. Una forma de hacerlo consiste en inmovilizar los subprocesos que no le interesan. En algunos escenarios, es posible que tenga que seguir un único subproceso sin inmovilizar el resto, por ejemplo, para reproducir un error determinado. Para seguir un subproceso sin inmovilizar los demás, debe evitar interrumpir el código, excepto en el subproceso que le interese. Puede realizar esta tarea estableciendo un punto de interrupción condicional.

Puede establecer puntos de interrupción en función de diferentes condiciones, como el nombre o el identificador del subproceso. Puede resultar útil establecer la condición en los datos que sabe que son únicos para cada subproceso. Este enfoque es común durante la depuración si le interesa más algún valor de datos concreto que cualquier subproceso concreto.

  1. Haga clic con el botón derecho en el punto de interrupción que ha creado antes y seleccione Condiciones.

  2. En la ventana Configuración del punto de interrupción, escriba data == 5 para la expresión condicional.

    Conditional Breakpoint

    Sugerencia

    Si está más interesado en un subproceso concreto, use un nombre o un identificador de subproceso para la condición. Para hacer esto en la ventana Configuración del punto de interrupción, seleccione Filtro en lugar de Expresión condicional y siga las sugerencias de filtro. Es posible que quiera asignar un nombre a los subprocesos del código de la aplicación, ya que los identificadores de subprocesos cambian al reiniciar el depurador.

  3. Cierre la ventana Configuración del punto de interrupción.

  4. Seleccione el botón Reiniciar Restart App para reiniciar la sesión de depuración.

    Se interrumpe el código en el subproceso en el que el valor de la variable de datos es 5. En la ventana Inspección paralela, busque la flecha de color amarillo que indica el contexto del depurador actual.

  5. Ahora, puede depurar el código paso a paso por procedimientos (F10) y paso a paso por instrucciones (F11), y seguir la ejecución del subproceso único.

    Siempre y cuando la condición de punto de interrupción sea única para el subproceso, y el depurador no alcance ningún otro punto de interrupción en otros subprocesos (es posible que tenga que deshabilitarlos), puede depurar el código paso a paso por procedimientos y por instrucciones sin cambiar a otros subprocesos.

    Nota:

    Al avanzar el depurador, se ejecutarán todos los subprocesos. Pero el depurador no interrumpirá el código en otros subprocesos a menos que uno llegue a un punto de interrupción.