Procedura dettagliata: debug di un'applicazione parallela

In questa procedura dettagliata viene illustrato come utilizzare le finestre Attività in parallelo e Stack in parallelo per eseguire il debug di un'applicazione parallela. Queste finestre aiutano a comprendere e a verificare il comportamento di runtime del codice che utilizza Task Parallel Library o il Runtime di concorrenza. Nella procedura dettagliata viene fornito un esempio di codice con punti di interruzione incorporati. Una volta interrotta l'esecuzione del codice, viene illustrato come esaminarlo utilizzando le finestre Attività in parallelo e Stack in parallelo.

Nella procedura dettagliata vengono spiegate le seguenti attività:

  • Come visualizzare gli stack di chiamate di tutti i thread in un'unica visualizzazione.

  • Come visualizzare l'elenco delle istanze di System.Threading.Tasks.Task create nell'applicazione.

  • Come visualizzare gli stack di chiamate reali delle attività anziché dei thread.

  • Come passare al codice dalle finestre Attività in parallelo e Stack in parallelo.

  • Come le finestre affrontano il ridimensionamento tramite il raggruppamento, lo zoom e altre funzionalità correlate.

Prerequisiti

È necessario che nel computer sia installato Visual Studio 2010.

In questa procedura dettagliata si presuppone che Just My Code sia abilitato. Nel menu Strumenti fare clic su Opzioni, espandere il nodo Debug, selezionare Generale, quindi Abilita Just My Code (solo gestito). Se non si imposta questa funzionalità, si può comunque utilizzare la procedura dettagliata, ma è possibile che i risultati differiscano dalle illustrazioni.

Esempio in C#

Se si utilizza l'esempio in C#, la procedura dettagliata presuppone inoltre che il codice esterno sia nascosto. Per attivare o disattivare la visualizzazione del codice esterno, fare clic con il pulsante destro del mouse sull'intestazione della tabella Nome nella finestra Stack di chiamate, quindi selezionare o deselezionare Mostra codice esterno. Se non si imposta questa funzionalità, si può comunque utilizzare la procedura dettagliata, ma è possibile che i risultati differiscano dalle illustrazioni.

Esempio in C++

Se si utilizza l'esempio in C++, è possibile ignorare i riferimenti al codice esterno contenuti in questo argomento. Il codice esterno si applica solo all'esempio in C#.

Illustrazioni

Le illustrazioni contenute in questo argomento sono state registrate in un computer quad core che esegue l'esempio in C#. Benché sia possibile utilizzare altre configurazioni per completare questa procedura dettagliata, le illustrazioni potrebbero differire dalle schermate visualizzate sul computer in uso.

Creazione del progetto di esempio

L'esempio di codice riportato in questa procedura dettagliata è relativo a un'applicazione che non esegue alcuna operazione. L'obiettivo è semplicemente comprendere come utilizzare le finestre degli strumenti per eseguire il debug di un'applicazione parallela.

Per creare il progetto di esempio

  1. Scegliere Nuovo dal menu File di Visual Studio, quindi Progetto.

  2. Nel riquadro Modelli installati selezionare Visual C#, Visual Basic o Visual C++. Per i linguaggi gestiti, assicurarsi che .NET Framework 4 sia visualizzato nella casella del framework.

  3. Selezionare Applicazione console, quindi fare clic su OK. Restare nella configurazione per il debug, ovvero l'impostazione predefinita.

  4. Aprire il file di codice con estensione CPP, CS o VB nel progetto. Eliminarne il contenuto per creare un file di codice vuoto.

  5. Incollare il seguente codice per il linguaggio selezionato nel file di codice vuoto.

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. Scegliere Salva tutto dal menu File.

  2. Scegliere Ricompila soluzione dal menu Compila.

    Notare che vi sono quattro chiamate a Debugger.Break (DebugBreak nell'esempio C++). Pertanto, non è necessario inserire punti di interruzione; la semplice esecuzione dell'applicazione determinerà fino a quattro interruzioni nel debugger.

Utilizzo della finestra Stack in parallelo: visualizzazione Thread

Scegliere Avvia debug dal menu Debug. Attendere che venga raggiunto il primo punto di interruzione.

Per visualizzare lo stack di chiamate di un singolo thread

  1. Scegliere Finestre dal menu Debug, quindi Thread. Ancorare la finestra Thread nella parte inferiore di Visual Studio.

  2. Scegliere Finestre dal menu Debug, quindi fare clic su Stack di chiamate. Ancorare la finestra Stack di chiamate nella parte inferiore di Visual Studio.

  3. Fare doppio clic su un thread nella finestra Thread per far sì che diventi il thread corrente. I thread correnti presentano una freccia gialla. Quando si modifica il thread corrente, il relativo stack di chiamate viene visualizzato nella finestra Stack di chiamate.

Per esaminare la finestra Stack in parallelo

  • Scegliere Finestre dal menu Debug, quindi fare clic su Stack in parallelo. Assicurarsi che Thread sia selezionato nella casella nell'angolo superiore sinistro.

    Tramite la finestra Stack in parallelo è possibile visualizzare contemporaneamente più stack di chiamate in un'unica visualizzazione. Nell'illustrazione che segue viene mostrata la finestra Stack in parallelo sopra alla finestra Stack di chiamate.

    Finestra Stack in parallelo nella visualizzazione thread

    Lo stack di chiamate del thread principale viene visualizzato in una casella, mentre gli stack di chiamate degli altri quattro thread sono raggruppati in un'altra casella. I quattro thread vengono raggruppati insieme in quanto i rispettivi stack frame condividono gli stessi contesti del metodo, vale a dire si trovano negli stessi metodi: A, B e C. Per visualizzare gli ID e i nomi dei thread che condividono la stessa casella, passare il mouse sull'intestazione (4 thread). Il thread corrente viene visualizzato in grassetto, come mostrato nell'illustrazione seguente.

    Descrizione comandi con nomi e ID del thread

    La freccia gialla indica lo stack frame attivo del thread corrente. Per ottenere ulteriori informazioni, passarvi sopra il mouse.

    Descrizione comandi nello stack frame attivo

    È possibile impostare i dettagli da visualizzare per gli stack frame (Nomi moduli, Tipi di parametro, Nomi parametri, Valori di parametro, Numeri di riga e Offset dei byte) facendo clic con il pulsante destro del mouse nella finestra Stack di chiamate.

    Un'evidenziazione blu attorno a una casella indica che il thread corrente è parte di quella casella. Il thread corrente è inoltre indicato dallo stack frame in grassetto nella descrizione comandi. Facendo doppio clic sul thread principale nella finestra Thread, si osserverà che l'evidenziazione blu nella finestra Stack in parallelo si sposta di conseguenza.

    Stack con il thread principale evidenziato in blu

Per riprendere l'esecuzione fino al secondo punto di interruzione

  • Per riprendere l'esecuzione fino al raggiungimento del secondo punto di interruzione, fare clic su Continua nel menu Debug. Nell'illustrazione seguente viene mostrata la struttura ad albero del thread in corrispondenza del secondo punto di interruzione.

    Finestra Stack in parallelo con molti rami

    In corrispondenza del primo punto di interruzione, i quattro thread erano passati tutti dal metodo S.A a S.B e a S.C. Tale informazione è ancora visibile nella finestra Stack in parallelo, ma i quattro thread sono avanzati. Uno di essi ha proseguito fino a S.D, quindi a S.E. Un altro ha proseguito fino a S.F, S.G e S.H. Gli altri due hanno proseguito fino a S.I e S.J, dopo di che uno è passato a S.K mentre l'altro ha proseguito fino al codice esterno non utente.

    È possibile passare il mouse sull'intestazione della casella, ad esempio 1 thread o 2 thread, per visualizzare gli ID dei thread. Passando invece il mouse sugli stack frame, verranno visualizzati gli ID dei thread e altri dettagli sui frame. L'evidenziazione blu indica il thread corrente, mentre la freccia gialla indica lo stack frame attivo del thread corrente.

    L'icona con i fili (linee ondulate di colore blu e rosso sovrapposte) indica gli stack frame attivi dei thread non correnti. Fare doppio clic su S.B nella finestra Stack di chiamate per passare da un frame all'altro. Nella finestra Stack in parallelo lo stack frame corrente del thread corrente è indicato tramite un'icona con una freccia circolare verde.

    Passare da un thread all'altro nella finestra Thread e osservare che la visualizzazione nella finestra Stack in parallelo viene aggiornata.

    Per passare a un altro thread, o a un altro frame di un altro thread, è possibile utilizzare il menu di scelta rapida nella finestra Stack in parallelo. Ad esempio, fare clic con il pulsante destro del mouse su S.J, scegliere Passa a frame, quindi fare clic su un comando.

    Percorso di esecuzione degli stack in parallelo

    Fare clic con il pulsante destro del mouse su S.C e scegliere Passa a frame. Uno dei comandi presenta un segno di spunta che indica lo stack frame del thread corrente. È possibile passare a quel frame dello stesso thread (si sposterà soltanto la freccia verde) oppure passare all'altro thread (si sposterà anche l'evidenziazione blu). Nell'illustrazione seguente viene mostrato il sottomenu.

    Menu degli stack con 2 opzioni su C mentre J è corrente

    Quando un contesto del metodo è associato a un unico stack frame, nell'intestazione della casella viene visualizzato 1 thread; fare doppio clic per passare a esso. Facendo doppio clic su un contesto del metodo al quale sono associati più frame, verrà visualizzato automaticamente il menu. Passando il mouse sui contesti del metodo, notare il triangolo nero sulla destra. Il menu di scelta rapida verrà visualizzato anche facendo clic su quel triangolo.

    Nel caso di applicazioni di grandi dimensioni con molti thread, è possibile concentrarsi su un unico sottoinsieme di thread. Nella finestra Stack in parallelo è possibile visualizzare gli stack di chiamate per i soli thread con contrassegno. Nella barra degli strumenti fare clic sul pulsante Mostra solo con contrassegno accanto alla casella di riepilogo.

    Finestra degli stack vuota e descrizione comandi

    Dopo di che, nella finestra Thread contrassegnare i thread uno a uno per vedere come vengono visualizzati i rispettivi stack di chiamate nella finestra Stack in parallelo. Per contrassegnare i thread, utilizzare il menu di scelta rapida o la prima cella di un thread. Fare nuovamente clic sul pulsante Mostra solo con contrassegno della barra degli strumenti per visualizzare tutti i thread.

Per riprendere l'esecuzione fino al terzo punto di interruzione

  1. Per riprendere l'esecuzione fino al raggiungimento del terzo punto di interruzione, fare clic su Continua nel menu Debug.

    Se più thread si trovano nello stesso metodo ma questo, a sua volta, non si trovava all'inizio dello stack di chiamate, il metodo viene visualizzato in caselle diverse. Un esempio in corrispondenza del punto di interruzione corrente è S.L, il quale contiene tre thread e viene visualizzato in tre caselle. Fare doppio clic su S.L.

    Percorso di esecuzione degli stack in parallelo

    Si noti che S.L appare in grassetto nelle altre due caselle, così da poter vedere in quali altri punti viene visualizzato. Per visualizzare quali frame chiamano in S.L e quali sono chiamati da S.L, fare clic sul pulsante Attiva/Disattiva visualizzazione metodo nella barra degli strumenti. Nell'illustrazione seguente viene mostrata la visualizzazione del metodo della finestra Stack in parallelo.

    Finestra degli stack nella visualizzazione metodo

    Si noti come il diagramma sia imperniato sul metodo selezionato e lo abbia posizionato nella propria casella al centro della visualizzazione. I chiamati e i chiamanti vengono visualizzati nella parte superiore e in quella inferiore. Fare nuovamente clic sul pulsante Attiva/Disattiva visualizzazione metodo per uscire da questa modalità.

    Il menu di scelta rapida della finestra Stack in parallelo presenta inoltre le voci descritte di seguito.

    • Visualizzazione esadecimale consente di scegliere tra il formato decimale e il formato esadecimale dei numeri nelle descrizioni comandi.

    • Informazioni sul caricamento dei simboli e Impostazioni simboli consentono di aprire le rispettive finestre di dialogo.

    • Vai a codice sorgente e Vai a disassembly consentono di passare al metodo selezionato nell'editor.

    • Mostra codice esterno consente di visualizzare tutti i frame anche se non si trovano nel codice utente. Provare questa voce per vedere il diagramma espandersi per accogliere i frame aggiuntivi, i quali possono essere disattivati in quanto non si dispone di simboli per tali frame.

    Quando si dispone di diagrammi di grandi dimensioni e si avanza al punto di interruzione successivo, è possibile far sì che la visualizzazione scorra automaticamente fino allo stack frame attivo del thread corrente, vale a dire il thread che per primo ha raggiunto il punto di interruzione. Nella finestra Stack in parallelo assicurarsi che il pulsante Scorrimento automatico a stack frame corrente nella barra degli strumenti sia attivato.

    Scorrimento automatico nella finestra Stack in parallelo

  2. Prima di continuare, nella finestra Stack in parallelo scorrere fino in fondo verso sinistra e verso il basso.

Per riprendere l'esecuzione fino al quarto punto di interruzione

  1. Per riprendere l'esecuzione fino al raggiungimento del quarto punto di interruzione, fare clic su Continua nel menu Debug.

    Si noti come la visualizzazione sia scorsa automaticamente fino al punto in questione. Passare da un thread all'altro nella finestra Thread o da uno stack frame all'altro nella finestra Stack di chiamate e notare come la visualizzazione scorra sempre automaticamente al frame corretto. Disattivare l'opzione Scorrimento automatico a stack frame corrente e osservare la differenza.

    La Visualizzazione panoramica risulta inoltre utile con diagrammi di grandi dimensioni nella finestra Stack in parallelo. Per vedere la Visualizzazione panoramica, fare clic sul pulsante tra le barre di scorrimento nell'angolo inferiore destro della finestra, come mostrato nell'illustrazione seguente.

    Finestra Stack in parallelo con visione panoramica

    È possibile spostare il rettangolo per avere una rapida panoramica del diagramma.

    Un altro modo per spostare il diagramma in una direzione qualsiasi è fare clic su un'area vuota del diagramma e trascinarlo nel punto desiderato.

    Per lo zoom avanti e indietro del diagramma, tenere premuto CTRL mentre si muove la rotella del mouse. In alternativa, fare clic sul pulsante Zoom nella barra degli strumenti, quindi utilizzare lo strumento Zoom.

    Stack affiancati con lo zoom avanti e indietro

    È inoltre possibile visualizzare gli stack dall'alto verso il basso, anziché dal basso verso l'alto. A tal proposito, fare clic sul menu Strumenti, scegliere Opzioni, quindi selezionare o deselezionare l'opzione nel nodo Debug.

  2. Prima di continuare, fare clic su Termina debug nel menu Debug per terminare l'esecuzione.

Utilizzo della finestra Attività in parallelo e della visualizzazione Attività della finestra Stack in parallelo

Prima di proseguire, si consiglia di completare le procedure precedenti.

Per riavviare l'applicazione fino al raggiungimento del primo punto di interruzione

  1. Nel menu Debug fare clic su Avvia debug e attendere il raggiungimento del primo punto di interruzione.

  2. Scegliere Finestre dal menu Debug, quindi Thread. Ancorare la finestra Thread nella parte inferiore di Visual Studio.

  3. Scegliere Finestre dal menu Debug, quindi Stack di chiamate. Ancorare la finestra Stack di chiamate nella parte inferiore di Visual Studio.

  4. Fare doppio clic su un thread nella finestra Thread per far sì che diventi il thread corrente. I thread correnti presentano la freccia gialla. Quando si modifica il thread corrente, le altre finestre vengono aggiornate. Si esamineranno quindi le attività.

  5. Scegliere Finestre dal menu Debug, quindi Attività in parallelo. Nell'illustrazione seguente viene mostrata la finestra Attività in parallelo.

    Finestra Attività in parallelo con 4 attività in esecuzione

    Per ogni attività in esecuzione è possibile visualizzare il relativo ID, restituito dalla proprietà omonima, l'ID e il nome del thread che la esegue e il relativo percorso (passando il mouse sul percorso viene visualizzata una descrizione comandi con l'intero stack di chiamate). Inoltre, nella colonna Attività è possibile visualizzare il metodo passato nell'attività, in altre parole il punto iniziale.

    Le colonne possono essere ordinate. Si noti il glifo di ordinamento che indica la colonna e la direzione di ordinamento. È anche possibile riordinare le colonne trascinandole a sinistra o a destra.

    La freccia gialla indica l'attività corrente. Per passare da un'attività all'altra, fare doppio clic su un'attività o utilizzare il menu di scelta rapida. Quando si passa da un'attività all'altra, il thread sottostante diventa quello corrente e le altre finestre vengono aggiornate.

    Quando si passa manualmente da un'attività a un'altra, la freccia gialla si sposta, ma una freccia bianca continua a mostrare l'attività che ha causato l'interruzione nel debugger.

Per riprendere l'esecuzione fino al secondo punto di interruzione

  • Per riprendere l'esecuzione fino al raggiungimento del secondo punto di interruzione, fare clic su Continua nel menu Debug.

    In precedenza, nella colonna Stato tutte le attività venivano indicate come in esecuzione, ma ora due attività sono in attesa. Le attività possono essere bloccate per molte ragioni diverse. Passare il mouse su un'attività in attesa nella colonna Stato per conoscere il motivo per cui è bloccata. Ad esempio, nell'illustrazione seguente, l'attività 3 è in attesa dell'attività 4.

    Finestra Attività in parallelo con 2 attività in attesa

    L'attività 4, a sua volta, è in attesa di un monitoraggio di proprietà del thread assegnato all'attività 2.

    Finestra delle attività con un'attività in attesa e descrizione comando

    È possibile contrassegnare un'attività facendo clic sul contrassegno nella prima colonna della finestra Attività in parallelo.

    È possibile utilizzare i contrassegni per tenere traccia delle attività tra i vari punti di interruzione nella stessa sessione di debug o per filtrare le attività i cui stack di chiamate vengono visualizzati nella finestra Stack in parallelo.

    Quando in precedenza si utilizzava la finestra Stack in parallelo, venivano visualizzati i thread dell'applicazione. Visualizzando nuovamente la finestra Stack in parallelo, questa volta si vedranno le attività dell'applicazione. A tale proposito, selezionare Attività nella casella in alto a sinistra. Nell'illustrazione che segue viene mostrata la visualizzazione Attività.

    Finestra Attività in parallelo nella visualizzazione attività

    I thread che al momento non eseguono attività non vengono mostrati nella visualizzazione Attività della finestra Stack in parallelo. Inoltre, per i thread che eseguono attività, alcuni stack frame che non sono importanti per le attività vengono filtrati dalla parte superiore e dalla parte inferiore dello stack.

    Visualizzare nuovamente la finestra Attività in parallelo. Fare clic con il pulsante destro del mouse su un'intestazione di colonna qualsiasi per visualizzare un menu di scelta rapida per la colonna.

    Attività in parallelo con il menu dell'intestazione colonna

    Il menu di scelta rapida può essere utilizzato per aggiungere o rimuovere colonne. Ad esempio, la colonna AppDomain non è selezionata, pertanto non viene visualizzata nell'elenco. Fare clic su Padre. Verrà visualizzata la colonna Padre senza valori per le quattro attività.

Per riprendere l'esecuzione fino al terzo punto di interruzione

  • Per riprendere l'esecuzione fino al raggiungimento del terzo punto di interruzione, fare clic su Continua nel menu Debug.

    Una nuova attività, l'attività 5, è ora in esecuzione mentre l'attività 4 è in attesa. Per visualizzare il motivo, passare il mouse sull'attività in attesa nella finestra Stato. Nella colonna Padre, si noti che l'attività 4 è l'attività padre dell'attività 5.

    Per visualizzare meglio la relazione padre-figlio, fare clic con il pulsante destro del mouse sull'intestazione di colonna Padre, quindi scegliere Visualizzazione padre/figlio. Si dovrebbe vedere l'illustrazione seguente.

    Visualizzazione Attività in parallelo nella visualizzazione padre/figlio

    Si noti che l'attività 4 e l'attività 5 sono in esecuzione nello stesso thread. Questa informazione non viene visualizzata nella finestra Thread; il fatto che appaia qui è un altro vantaggio della finestra Attività in parallelo. A conferma di questo, visualizzare la finestra Stack in parallelo. Assicurarsi di essere nella visualizzazione Attività. Individuare le attività 4 e 5 facendo doppio clic su di esse nella finestra Attività in parallelo. L'evidenziazione blu nella finestra Stack in parallelo verrà di conseguenza aggiornata. È anche possibile individuare le attività 4 e 5 analizzando le descrizioni comandi nella finestra Stack in parallelo.

    Finestra Stack in parallelo nella visualizzazione attività

    Fare clic con il pulsante destro del mouse su S.P nella finestra Stack in parallelo, quindi scegliere Passa a thread. La finestra passerà alla visualizzazione Thread e verrà visualizzato il thread corrispondente. È possibile vedere entrambe le attività nello stesso thread.

    Visualizzazione thread con thread evidenziato

    Questo è un altro vantaggio della visualizzazione Attività nella finestra Stack in parallelo rispetto alla finestra Thread.

Per riprendere l'esecuzione fino al quarto punto di interruzione

  • Per riprendere l'esecuzione fino al raggiungimento del quarto punto di interruzione, fare clic su Continua nel menu Debug. Fare clic sull'intestazione di colonna ID per ordinare in base all'ID. Si dovrebbe vedere l'illustrazione seguente.

    Finestra Stack in parallelo con attività in 4 stati

    Essendo stata completata, l'attività 5 non viene più visualizzata. Se ciò non avviene nel computer in uso e il deadlock non viene visualizzato, avanzare di un passaggio premendo F11.

    Le attività 3 e 4 sono ora in attesa una dell'altra e sono in deadlock. Vi sono inoltre 5 nuove attività che sono attività figlio dell'attività 2 e vengono ora pianificate. Le attività pianificate sono attività avviate nel codice ma non ancora eseguite. Le relative colonne Percorso e Assegnazione thread sono pertanto vuote.

    Visualizzare nuovamente la finestra Stack in parallelo. L'intestazione di ogni casella presenta una descrizione comandi nella quale sono visualizzati gli ID e i nomi dei thread. Passare alla visualizzazione Attività nella finestra Stack in parallelo. Passare il mouse su un'intestazione per visualizzare l'ID, il nome e lo stato dell'attività come mostrato nell'illustrazione seguente.

    Finestra Stack in parallelo con la descrizione comandi dell'intestazione

    È possibile raggruppare le attività in base alle colonne. Nella finestra Attività in parallelo fare clic con il pulsante destro del mouse sull'intestazione di colonna Stato, quindi scegliere Raggruppa per stato. Nell'illustrazione seguente viene mostrata la finestra Attività in parallelo raggruppata in base allo stato.

    Finestra Attività in parallelo con attività raggruppate

    È anche possibile raggruppare in base alle altre colonne. Raggruppando le attività, è possibile concentrarsi su un sottoinsieme di attività. Ogni gruppo comprimibile presenta un conteggio degli elementi raggruppati insieme. È anche possibile contrassegnare rapidamente tutti gli elementi nel gruppo facendo clic sul pulsante Contrassegna a destra del pulsante Comprimi.

    Finestra Attività in parallelo raggruppata

    L'ultima funzionalità della finestra Attività in parallelo da esaminare è il menu di scelta rapida che viene visualizzato facendo clic con il pulsante destro del mouse su un'attività.

    Finestra Attività in parallelo con menu di scelta rapida espanso

    Il menu di scelta rapida visualizza comandi diversi a seconda dello stato dell'attività. I comandi possono includere Copia, Seleziona tutto, Visualizzazione esadecimale, Passa ad attività, Blocca thread assegnato, Blocca tutti i thread ad eccezione di quello corrente, Sblocca thread assegnato e Contrassegna.

    È possibile bloccare il thread sottostante di un'attività o di più attività oppure bloccare tutti i thread a eccezione di quello assegnato. Nelle finestre Attività in parallelo e Thread, i thread bloccati vengono rappresentati allo stesso modo, vale a dire da un'icona di sospensione blu.

In questa procedura dettagliata sono state illustrate le finestre del debugger Attività in parallelo e Stack in parallelo. Utilizzare queste finestre con progetti reali che a loro volta utilizzano codice multithreading. È possibile esaminare codice parallelo scritto in C++, C# o Visual Basic.

Vedere anche

Attività

Procedura dettagliata: debug di un'applicazione parallela

Utilizzo della finestra Stack in parallelo

Utilizzo della finestra Attività in parallelo

Concetti

Programmazione parallela in .NET Framework

Runtime di concorrenza

Altre risorse

Guida di orientamento al debugger

Debug del codice gestito