Partager via


Procédure pas à pas : débogage d'une application parallèle

Cette procédure pas à pas indique comment utiliser les fenêtres Tâches parallèles et Piles parallèles pour déboguer une application parallèle. Ces fenêtres vous aident à comprendre et à vérifier le comportement au moment de l'exécution du code qui utilise la Bibliothèque parallèle de tâches ou le Concurrency Runtime. Cette procédure pas à pas fournit un exemple de code qui comporte des points d'arrêt intégrés. Une fois le code arrêté, la procédure pas à pas indique comment utiliser les fenêtres Tâches parallèles et Piles parallèles pour l'examiner.

Cette procédure pas à pas aborde les tâches suivantes :

  • Comment afficher les piles d'appels de tous les threads dans une vue.

  • Comment afficher la liste des instances System.Threading.Tasks.Task créées dans votre application.

  • Comment afficher les véritables piles d'appels des tâches au lieu des threads.

  • Comment accéder au code à partir des fenêtres Tâches parallèles et Piles parallèles.

  • Comment les fenêtres gèrent l'échelle avec les fonctionnalités de regroupement, de zoom et autres.

Composants requis

Visual Studio 2010 doit être installé sur votre ordinateur.

Cette procédure pas à pas suppose que l'option Uniquement mon code est activée. Dans le menu Outils, cliquez sur Options, développez le nœud Débogage, sélectionnez Général, puis choisissez Activer Uniquement mon code (Managé uniquement). Si vous ne définissez pas cette fonctionnalité, vous pouvez quand même effectuer cette procédure pas à pas, mais vos résultats peuvent différer de ceux des illustrations.

Exemple C#

Si vous utilisez l'exemple C#, cette procédure pas à pas suppose également que le code externe est masqué. Pour afficher ou masquer le code externe, cliquez avec le bouton droit sur l'en-tête de table Nom de la fenêtre Pile des appels, puis activez ou désactivez Afficher le code externe. Si vous ne définissez pas cette fonctionnalité, vous pouvez quand même effectuer cette procédure pas à pas, mais vos résultats peuvent différer de ceux des illustrations.

Exemple C++

Si vous utilisez l'exemple C++, vous pouvez ignorer les références au code externe de cette rubrique. Le code externe s'applique uniquement à l'exemple C#.

Illustrations

Les illustrations de cette rubrique ont été enregistrées sur un ordinateur quadricœur exécutant l'exemple C#. Bien que vous puissiez utiliser d'autres configurations pour effectuer cette procédure pas à pas, les illustrations peuvent différer de celles affichées sur votre ordinateur.

Création de l'exemple de projet

L'exemple de code de cette procédure pas à pas est relatif à une application qui ne fait rien. L'objectif est simplement de comprendre comment utiliser les fenêtres Outils pour déboguer une application parallèle.

Pour créer l'exemple de projet

  1. Dans le menu Fichier de Visual Studio, pointez sur Nouveau, puis cliquez sur Projet.

  2. Dans le volet Modèles installés, sélectionnez Visual C#, Visual Basic ou Visual C++. Pour les langages managés, assurez-vous que .NET Framework 4 est affiché dans la fenêtre .NET Framework.

  3. Sélectionnez Application console et cliquez sur OK. Restez en configuration Debug, qui est la valeur par défaut.

  4. Ouvrez le fichier de code .cpp, .cs ou .vb dans le projet. Supprimez son contenu pour créer un fichier de code vide.

  5. Collez le code suivant dans le langage choisi dans le fichier de code vide.

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. Dans le menu Fichier, cliquez sur Enregistrer tout.

  2. Dans le menu Générer, cliquez sur Régénérer la solution.

    Notez qu'il y a quatre appels à Debugger.Break (DebugBreak dans l'exemple C++). Vous n'avez donc pas à insérer de points d'arrêt. La simple exécution de l'application entraînera son arrêt dans le débogueur jusqu'à quatre fois.

Utilisation de la fenêtre Piles parallèles : vue Threads

Dans le menu Déboguer, cliquez sur Démarrer le débogage. Attendez que le premier point d'arrêt soit atteint.

Pour afficher la pile des appels d'un thread unique

  1. Dans le menu Déboguer, pointez sur Fenêtres, puis cliquez sur Threads. Ancrez la fenêtre Threads en bas de Visual Studio.

  2. Dans le menu Déboguer, pointez sur Fenêtres, puis cliquez sur Pile des appels. Ancrez la fenêtre Pile des appels en bas de Visual Studio.

  3. Double-cliquez sur un thread dans la fenêtre Threads pour le rendre actuel. Les threads actuels comportent une flèche jaune. Lorsque vous modifiez le thread actuel, sa pile des appels est affichée dans la fenêtre Pile des appels.

Pour examiner la fenêtre Piles parallèles

  • Dans le menu Déboguer, pointez sur Fenêtres, puis cliquez sur Piles parallèles. Assurez-vous que l'option Threads est sélectionnée dans la zone située dans l'angle supérieur gauche.

    Grâce à la fenêtre Piles parallèles, vous pouvez afficher plusieurs piles d'appels en même temps au sein d'une seule vue. L'illustration suivante présente la fenêtre Piles parallèles au-dessus de la fenêtre Pile des appels.

    Fenêtre Piles parallèles dans la vue Threads

    La pile des appels du thread principal s'affiche dans une zone et les piles des appels des quatre autres threads sont regroupées dans une autre zone. Quatre threads sont regroupés car leurs frames de pile partagent les mêmes contextes de méthode, ce qui signifie qu'ils se trouvent dans les mêmes méthodes : A, B et C. Pour afficher les ID et noms de threads qui partagent la même zone, pointez sur l'en-tête (4 Threads). Le thread actuel est affiché en gras, comme indiqué dans l'illustration suivante.

    Info-bulle avec des ID de thread et des noms

    La flèche jaune indique le frame de pile actif du thread actuel. Pour obtenir plus d'informations, placez votre pointeur au-dessus.

    Info-bulle sur le frame de pile actif

    Vous pouvez indiquer le détail souhaité pour les frames de pile (Afficher les noms de modules, Afficher les types de paramètre, Afficher les noms de paramètres, Afficher les valeurs de paramètre, Afficher les numéros de ligne et Afficher les offsets d'octet) en cliquant avec le bouton droit dans la fenêtre Pile des appels.

    Une surbrillance bleue autour d'une zone indique que le thread actuel fait partie de cette zone. Le thread actuel est également indiqué par le frame de pile en gras dans l'info-bulle. Si vous double-cliquez sur le thread principal dans la fenêtre Threads, vous pouvez observer que la surbrillance bleue dans la fenêtre Piles parallèles se déplace en conséquence.

    Piles avec le thread principal mis en surbrillance en bleu

Pour continuer l'exécution jusqu'au deuxième point d'arrêt

  • Pour reprendre l'exécution jusqu'au deuxième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer. L'illustration suivante présente l'arborescence des threads au deuxième point d'arrêt.

    Fenêtre Piles parallèles avec de nombreuses branches

    Au premier point d'arrêt, les quatre threads sont tous allés de la méthode S.A à S.B et S.C. Ces informations sont toujours visibles dans la fenêtre Piles parallèles, mais les quatre threads ont depuis progressé plus loin. L'un d'entre eux a continué vers S.D, puis S.E. Un autre est allé vers S.F, S.G et S.H. Deux autres ont continué vers S.I et S.J et, de là, l'un d'eux est allé vers S.K et l'autre est parti vers du code externe non-utilisateur.

    Vous pouvez pointer sur l'en-tête de zone, par exemple 1 Thread ou 2 Threads, pour afficher les ID des threads. Vous pouvez pointer sur des frames de pile pour afficher les ID des threads et d'autres détails sur les frames. La surbrillance bleue indique le thread actuel et la flèche jaune indique le frame de pile actif du thread actuel.

    L'icône de maillage (lignes ondulées bleue et rouge se chevauchant) indique les frames de pile actifs des threads non actuels. Dans la fenêtre Pile des appels, double-cliquez sur S.B pour basculer des frames. La fenêtre Piles parallèles indique le frame de pile actuel du thread actuel à l'aide d'une icône de flèche incurvée verte.

    Dans la fenêtre Threads, basculez entre les threads et remarquez que la vue de la fenêtre Piles parallèles est mise à jour.

    Vous pouvez basculer vers un autre thread, ou vers un autre frame d'un autre thread, à l'aide du menu contextuel de la fenêtre Piles parallèles. Par exemple, cliquez avec le bouton droit sur S.J, pointez sur Basculer vers le frame, puis cliquez sur une commande.

    Chemin d'exécution des piles parallèles

    Cliquez avec le bouton droit sur S.C et pointez sur Basculer vers le frame. L'une des commandes comporte une coche qui indique le frame de pile du thread actuel. Vous pouvez basculer vers ce frame du même thread (seule la flèche verte se déplace) ou vous pouvez basculer vers l'autre thread (la surbrillance bleue se déplace également). L'illustration suivante présente le sous-menu.

    Menu Piles avec 2 options sur C alors que J est actif

    Lorsqu'un contexte de méthode est associé à un seul frame de pile, l'en-tête de zone indique 1 Thread et vous pouvez basculer vers lui en double-cliquant. Si vous double-cliquez sur un contexte de méthode associé à plus d'1 frame, le menu apparaît automatiquement. Lorsque vous pointez sur les contextes de méthode, remarquez le triangle noir à droite. Le fait de cliquer sur ce triangle affiche également le menu contextuel.

    Pour les grandes applications qui comportent de nombreux threads, vous pouvez vous concentrer sur un seul sous-ensemble de threads. La fenêtre Piles parallèles peut afficher des piles d'appels uniquement pour les threads avec indicateur. Dans la barre d'outils, cliquez sur le bouton Afficher uniquement les threads avec indicateur situé à côté de la zone de liste.

    Fenêtre des piles vides et info-bulle

    Ensuite, dans la fenêtre Threads, signalez les threads un par un pour voir comment leurs piles d'appels s'affichent dans la fenêtre Piles parallèles. Pour signaler des threads, utilisez le menu contextuel ou la première cellule d'un thread. Cliquez à nouveau sur le bouton de barre d'outils Afficher uniquement les threads avec indicateur pour afficher tous les threads.

Pour continuer l'exécution jusqu'au troisième point d'arrêt

  1. Pour reprendre l'exécution jusqu'au troisième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer.

    Lorsque plusieurs threads se trouvent dans la même méthode mais que cette méthode ne figure pas au début de la pile des appels, la méthode s'affiche dans des zones différentes. Un exemple au point d'arrêt actuel est S.L, qui possède trois threads et apparaît dans trois zones. Double-cliquez sur S.L.

    Chemin d'exécution des piles parallèles

    Remarquez que S.L apparaît en gras dans les deux autres zones afin que vous puissiez voir où il s'affiche. Si vous souhaitez voir quels frames appeler dans S.L et quels frames sont appelés, cliquez sur le bouton Basculer dans la vue Méthode de la barre d'outils. L'illustration suivante présente la vue Méthode de la fenêtre Piles parallèles.

    Fenêtre Piles dans la vue Méthode

    Remarquez comment le diagramme a pivoté sur la méthode sélectionnée et l'a positionnée dans sa propre zone au milieu de la vue. Les appelés et appelants s'affichent en haut et en bas. Cliquez à nouveau sur le bouton Basculer dans la vue Méthode pour quitter ce mode.

    Le menu contextuel de la fenêtre Piles parallèles comporte également les éléments suivants.

    • Affichage hexadécimal bascule les nombres apparaissant dans les info-bulles entre affichage décimal et hexadécimal.

    • Informations sur le chargement de symboles et Paramètres des symboles ouvrent leurs boîtes de dialogue respectives.

    • Atteindre le code source et Atteindre le code machine permettent de naviguer dans l'éditeur vers la méthode sélectionnée.

    • Afficher le code externe affiche tous les frames même s'ils ne figurent pas dans le code utilisateur. Essayez cet élément pour voir le diagramme se développer pour accueillir les frames supplémentaires (qui peuvent être grisés car vous n'avez pas de symboles pour eux).

    Lorsque vous possédez de grands diagrammes et que vous accédez au point d'arrêt suivant, vous pouvez souhaiter que la vue défile automatiquement vers le frame de pile actif du thread actuel, à savoir le thread qui a atteint en premier le point d'arrêt. Dans la fenêtre Piles parallèles, assurez-vous que le bouton Défilement automatique vers le frame de pile actif de la barre d'outils est activé.

    Défilement automatique dans la fenêtre Piles parallèles

  2. Avant de continuer, dans la fenêtre Piles parallèles, défilez tout à gauche et tout en bas.

Pour continuer l'exécution jusqu'au quatrième point d'arrêt

  1. Pour reprendre l'exécution jusqu'au quatrième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer.

    Remarquez comment la vue défile automatiquement. Basculez des threads dans la fenêtre Threads ou basculez des frames de pile dans la fenêtre Pile des appels. Notez comment la vue défile toujours automatiquement vers le frame approprié. Désactivez l'option Défilement automatique vers le frame de pile actif et notez la différence.

    La Vue aérienne est également utile avec les diagrammes de taille importante dans la fenêtre Piles parallèles. Vous pouvez afficher la Vue aérienne en cliquant sur le bouton situé entre les barres de défilement dans l'angle inférieur droit de la fenêtre, comme indiqué dans l'illustration suivante.

    Fenêtre Piles parallèles avec vue aérienne

    Vous pouvez déplacer le rectangle pour effectuer un panoramique rapide autour du diagramme.

    Une autre façon de déplacer le diagramme consiste à cliquer dans une zone vide de celui-ci et à le faire glisser où vous le souhaitez.

    Pour effectuer un zoom avant ou arrière dans le diagramme, appuyez sur CTRL et maintenez cette touche enfoncée pendant que vous déplacez la roulette de la souris. Vous pouvez également cliquer sur le bouton Zoom de la barre d'outils, puis utiliser l'outil Zoom.

    Piles côte à côte avec zooms avant et arrière effectués

    Vous pouvez également afficher les piles dans le sens haut/bas, plutôt que bas/haut. Pour cela, dans le menu Outils, cliquez sur Options, puis activez ou désactivez l'option sous le nœud Débogage.

  2. Avant de continuer, dans le menu Déboguer, cliquez sur Arrêter le débogage pour arrêter l'exécution.

Utilisation de la fenêtre Tâches parallèles et de la vue Tâches de la fenêtre Piles parallèles

Nous vous recommandons d'effectuer les procédures précédentes avant de continuer.

Pour redémarrer l'application jusqu'à ce que le premier point d'arrêt soit atteint

  1. Dans le menu Déboguer, cliquez sur Démarrer le débogage et attendez le premier point d'arrêt soit atteint.

  2. Dans le menu Déboguer, pointez sur Fenêtres, puis cliquez sur Threads. Ancrez la fenêtre Threads en bas de Visual Studio.

  3. Dans le menu Déboguer, pointez sur Fenêtres et cliquez sur Pile des appels. Ancrez la fenêtre Pile des appels en bas de Visual Studio.

  4. Double-cliquez sur un thread dans la fenêtre Threads pour le rendre actuel. Les threads actuels comportent une flèche jaune. Lorsque vous modifiez le thread actuel, les autres fenêtres sont mises à jour. Nous allons maintenant examiner les tâches.

  5. Dans le menu Déboguer, pointez sur Fenêtres, puis cliquez sur Tâches parallèles. L'illustration suivante présente la fenêtre Tâches parallèles.

    Fenêtre Tâches parallèles avec 4 tâches en cours d'exécution

    Pour chaque tâche en cours, vous pouvez lire l'ID, retourné par la propriété de même nom, l'ID et le nom du thread qui l'exécute, ainsi que son emplacement (le fait de pointer dessus affiche une info-bulle qui comporte l'ensemble de la pile des appels). Dans la colonne Tâche, vous pouvez également voir la méthode qui a été passée dans la tâche; en d'autres termes, le point de début.

    Vous pouvez trier toutes les colonnes. Remarquez le glyphe de tri qui indique la colonne et le sens du tri. Vous pouvez également réorganiser les colonnes en les faisant glisser à gauche ou à droite.

    La flèche jaune indique la tâche actuelle. Vous pouvez basculer des tâches en double-cliquant dessus ou en utilisant le menu contextuel. Lorsque vous basculez des tâches, le thread sous-jacent devient le thread actuel et les autres fenêtres sont mises à jour.

    Lorsque vous basculez manuellement d'une tâche à une autre, la flèche jaune se déplace, mais une flèche blanche indique toujours la tâche qui a provoqué l'arrêt du débogueur.

Pour continuer l'exécution jusqu'au deuxième point d'arrêt

  • Pour reprendre l'exécution jusqu'au deuxième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer.

    Auparavant, la colonne État indiquait toutes les tâches comme étant En cours d'exécution, mais maintenant deux tâches ont l'état En attente. Les tâches peuvent être bloquées pour de nombreuses raisons. Dans la colonne État, pointez sur une tâche en attente pour savoir pourquoi elle est bloquée. Par exemple, dans l'illustration suivante, la tâche 3 attend la tâche 4.

    Fenêtre Tâches parallèles avec 2 tâches en attente

    La tâche 4, ensuite, attend un gestionnaire possédé par le thread assigné à la tâche 2.

    Fenêtre Tâches avec tâche en attente et info-bulle

    Vous pouvez signaler une tâche en cliquant sur l'indicateur dans la première colonne de la fenêtre Tâches parallèles.

    Vous pouvez utiliser un indicateur pour effectuer le suivi des tâches entre différents points d'arrêt d'une même session de débogage ou pour filtrer les tâches dont les piles d'appels sont affichées dans la fenêtre Piles parallèles.

    Lorsque vous avez utilisé la fenêtre Piles parallèles auparavant, vous avez affiché les threads d'application. Affichez à nouveau la fenêtre Piles parallèles, mais cette fois, visualisez les tâches d'application. Pour cela, sélectionnez Tâches dans la zone dans l'angle supérieur gauche. L'illustration suivante présente la vue Tâches.

    Fenêtre Tâches parallèles dans la vue Tâches

    Les threads qui n'exécutent actuellement pas de tâches n'apparaissent pas dans la vue Tâches de la fenêtre Piles parallèles. En outre, pour les threads qui exécutent des tâches, certains des frames de pile qui ne sont pas associés aux tâches sont filtrés à partir du haut et bas de la pile.

    Ouvrez à nouveau la fenêtre Tâches parallèles. Cliquez avec le bouton droit sur un en-tête de colonne pour afficher un menu contextuel pour cette colonne.

    Menu de l'en-tête de colonne Tâches parallèles

    Vous pouvez utiliser le menu contextuel pour ajouter ou supprimer des colonnes. Par exemple, la colonne AppDomain n'est pas sélectionnée. Elle ne s'affiche donc pas dans la liste. Cliquez sur Parent. La colonne Parent s'affiche sans valeur pour chacune des quatre tâches.

Pour continuer l'exécution jusqu'au troisième point d'arrêt

  • Pour reprendre l'exécution jusqu'au troisième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer.

    Une nouvelle tâche, la tâche 5, est en cours d'exécution et la tâche 4 est maintenant en attente. Vous pouvez voir pourquoi en pointant sur la tâche en attente dans la fenêtre État. Dans la colonne Parent, remarquez que la tâche 4 est la tâche parent de la tâche 5.

    Pour mieux visualiser la relation parent-enfant, cliquez avec le bouton droit sur l'en-tête de colonne Parent, puis cliquez sur Vue Parent enfant. L'illustration suivante doit apparaître.

    Vue des tâches parallèles dans la vue parent-enfant

    Remarquez que la tâche 4 et la tâche 5 s'exécutent sur le même thread. Ces informations ne sont pas affichées dans la fenêtre Threads. Leur affichage ici constitue un autre avantage de la fenêtre Tâches parallèles. Pour confirmer ceci, affichez la fenêtre Piles parallèles. Assurez-vous que vous visualisez bien la vue Tâches. Localisez les tâches 4 et 5 en double-cliquant dessus dans la fenêtre Tâches parallèles. La surbrillance bleue de la fenêtre Piles parallèles est alors mise à jour. Vous pouvez également localiser les tâches 4 et 5 en analysant les info-bulles de la fenêtre Piles parallèles.

    Fenêtre Piles parallèles dans la vue Tâches

    Dans la fenêtre Piles parallèles, cliquez avec le bouton droit sur S.P, puis cliquez sur Atteindre le thread. La fenêtre passe à la vue Threads et le frame correspondant s'affiche. Vous pouvez voir les deux tâches sur le même thread.

    Vue Threads avec thread mis en surbrillance

    Ceci est un autre avantage de la vue Tâches de la fenêtre Piles parallèles par rapport à la fenêtre Threads.

Pour continuer l'exécution jusqu'au quatrième point d'arrêt

  • Pour reprendre l'exécution jusqu'au troisième point d'arrêt, dans le menu Déboguer, cliquez sur Continuer. Cliquez sur l'en-tête de colonne ID pour trier par ID. L'illustration suivante doit apparaître.

    Fenêtre Piles parallèles avec des tâches dans 4 états

    Étant donné que la tâche 5 est terminée, elle n'est plus affichée. Si ce n'est pas le cas sur votre ordinateur et que l'interblocage n'est pas indiqué, appuyez sur F11.

    Les tâches 3 et 4 s'attendent maintenant l'une l'autre et sont bloquées. Il y a également 5 nouvelles tâches qui sont des enfants de la tâche 2 et qui sont maintenant planifiées. Les tâches planifiées sont des tâches qui ont été démarrées dans le code mais qui n'ont pas encore été exécutées. Par conséquent, leurs colonnes Emplacement et Affectation de thread sont vides.

    Ouvrez à nouveau la fenêtre Piles parallèles. L'en-tête de chaque zone comporte une info-bulle qui affiche les ID et noms de thread. Basculez en vue Tâches dans la fenêtre Piles parallèles. Pointez sur un en-tête pour afficher l'ID et le nom de la tâche, ainsi que son état, comme indiqué dans l'illustration suivante.

    Fenêtre Piles parallèles avec l'info-bulle d'en-tête

    Vous pouvez regrouper les tâches par colonne. Dans la fenêtre Tâches parallèles, cliquez avec le bouton droit sur l'en-tête de colonne État, puis cliquez sur Grouper par État. L'illustration suivante présente la fenêtre Tâches parallèles regroupée par état.

    Fenêtre Tâches parallèles avec des tâches groupées

    Vous pouvez également regrouper les tâches en fonction d'une autre colonne. Cela vous permet de vous concentrer sur un sous-ensemble de tâches. Chaque groupe réductible comporte un certain nombre des éléments regroupés ensemble. Vous pouvez également signaler rapidement tous les éléments du groupe en cliquant sur le bouton Marquer à droite du bouton Réduire.

    Fenêtre Tâches parallèles groupée

    La dernière fonctionnalité de la fenêtre Tâches parallèles à examiner est le menu contextuel qui s'affiche lorsque vous cliquez avec le bouton droit sur une tâche.

    Menu contextuel de la fenêtre Tâches parallèles développé

    Ce menu contextuel contient différentes commandes en fonction de l'état de la tâche. Ces commandes sont Copier, Sélectionner tout, Affichage hexadécimal, Basculer vers la tâche, Verrouiller le thread affecté, Verrouiller tous les threads sauf celui-ci, Libérer le thread affecté et Marquer.

    Vous pouvez verrouiller le thread sous-jacent d'une ou plusieurs tâches, ainsi que verrouiller tous les threads sauf celui qui est assigné. Un thread verrouillé est représenté dans la fenêtre Tâches parallèles comme dans la fenêtre Threads, par une icône de pause bleue.

Résumé

Cette procédure pas à pas a présenté les fenêtres de débogage Tâches parallèles et Piles parallèles. Vous pouvez utiliser ces fenêtres sur de véritables projets qui utilisent du code multithread. Vous pouvez examiner le code parallèle rédigé en C++, C# ou Visual Basic.

Voir aussi

Tâches

Procédure pas à pas : débogage d'une application parallèle

Utilisation de la fenêtre Piles parallèles

Utilisation de la fenêtre Tâches parallèles

Concepts

Programmation parallèle dans le .NET Framework

Concurrency Runtime

Autres ressources

Présentation du débogueur

Débogage du code managé