Przewodnik: debugowanie aplikacji równoległej w programie Visual Studio (C#, Visual Basic, C++)

W tym przewodniku pokazano, jak debugować aplikację równoległą przy użyciu okien Równoległe zadania i stosy równoległe . Te okna pomagają zrozumieć i zweryfikować zachowanie kodu w czasie wykonywania, który używa biblioteki równoległej zadań (TPL) lub środowiska uruchomieniowego współbieżności. Ten przewodnik zawiera przykładowy kod z wbudowanymi punktami przerwania. Po przerwach kodu w przewodniku pokazano, jak używać okien Zadań równoległych i stosów równoległych do jego zbadania.

W tym przewodniku przedstawiono następujące zadania:

  • Jak wyświetlić stosy wywołań wszystkich wątków w jednym widoku.

  • Jak wyświetlić listę wystąpień utworzonych System.Threading.Tasks.Task w aplikacji.

  • Sposób wyświetlania rzeczywistych stosów wywołań zadań zamiast wątków.

  • Jak przejść do kodu z okien Równoległe zadania i stosy równoległe .

  • Jak okna radzą sobie ze skalowaniem poprzez grupowanie, powiększanie i inne powiązane funkcje.

Wymagania wstępne

W tym przewodniku przyjęto założenie, że opcja Tylko mój kod jest włączona (domyślnie jest włączona w nowszych wersjach programu Visual Studio). W menu Narzędzia wybierz pozycję Opcje, rozwiń węzeł Debugowanie, wybierz pozycję Ogólne, a następnie wybierz pozycję Włącz tylko mój kod (tylko zarządzany). Jeśli nie ustawisz tej funkcji, nadal możesz użyć tego przewodnika, ale wyniki mogą się różnić od ilustracji.

Przykład w języku C#

Jeśli używasz przykładu w języku C#, w tym przewodniku założono również, że kod zewnętrzny jest ukryty. Aby przełączyć, czy kod zewnętrzny jest wyświetlany, kliknij prawym przyciskiem myszy nagłówek tabeli Nazwa okna stosu wywołań, a następnie wybierz lub wyczyść pole Wyboru Pokaż kod zewnętrzny. Jeśli nie ustawisz tej funkcji, nadal możesz użyć tego przewodnika, ale wyniki mogą się różnić od ilustracji.

Przykład języka C++

Jeśli używasz przykładu języka C++, możesz zignorować odwołania do kodu zewnętrznego w tym artykule. Kod zewnętrzny dotyczy tylko przykładu języka C#.

Ilustracje

Ilustracje w tym artykule są rejestrowane na czterordzeniowym komputerze z uruchomionym przykładem języka C#. Mimo że można użyć innych konfiguracji do ukończenia tego przewodnika, ilustracje mogą różnić się od wyświetlanych na komputerze.

Tworzenie przykładowego projektu

Przykładowy kod w tym przewodniku dotyczy aplikacji, która nic nie robi. Celem ćwiczenia jest zrozumienie, jak używać okien narzędzi do debugowania aplikacji równoległej.

  1. Otwórz program Visual Studio i utwórz nowy projekt.

    Jeśli okno uruchamiania nie jest otwarte, wybierz pozycję Okno uruchamiania pliku>.

    W oknie startowym wybierz pozycję Nowy projekt.

    W oknie uruchamiania wybierz pozycję Utwórz nowy projekt.

    W oknie Tworzenie nowego projektu wprowadź lub wpisz konsolę w polu wyszukiwania. Następnie wybierz pozycję C#, C++lub Visual Basic z listy Język, a następnie wybierz pozycję Windows z listy Platforma.

    Po zastosowaniu filtrów języka i platformy wybierz aplikację konsolową dla platformy .NET Core lub C++, a następnie wybierz przycisk Dalej.

    Uwaga

    Jeśli nie widzisz poprawnego szablonu, przejdź do pozycji Narzędzia Pobierz narzędzia>i funkcje..., co spowoduje otwarcie Instalator programu Visual Studio. Wybierz pakiet roboczy Programowanie aplikacji klasycznych .NET lub Programowanie aplikacji klasycznych przy użyciu języka C++, a następnie wybierz pozycję Modyfikuj.

    W oknie Konfigurowanie nowego projektu wpisz nazwę lub użyj nazwy domyślnej w polu Nazwa projektu. Następnie wybierz pozycję Dalej lub Utwórz, niezależnie od dostępnej opcji.

    W przypadku platformy .NET Core wybierz zalecaną platformę docelową lub platformę .NET 8, a następnie wybierz pozycję Utwórz.

    Zostanie wyświetlony nowy projekt konsoli. Po utworzeniu projektu zostanie wyświetlony plik źródłowy.

  2. Otwórz plik kodu .cpp, cs lub vb w projekcie. Usuń jego zawartość, aby utworzyć pusty plik kodu.

  3. Wklej następujący kod wybranego języka do pustego pliku kodu.

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

Po zaktualizowaniu pliku kodu zapisz zmiany i skompiluj rozwiązanie.

  1. W menu File (Plik) wybierz polecenie Save all (Zapisz wszystko).

  2. W menu Kompilacja wybierz pozycję Kompiluj ponownie rozwiązanie.

Zwróć uwagę, że istnieją cztery wywołania funkcji Debugger.Break (DebugBreak w przykładzie języka C++). W związku z tym nie trzeba wstawiać punktów przerwania. Po prostu uruchomienie aplikacji powoduje przerwanie debugera do czterech razy.

Korzystanie z okna stosów równoległych: widok wątków

Aby rozpocząć, w menu Debugowanie wybierz pozycję Rozpocznij debugowanie. Poczekaj na trafienie pierwszego punktu przerwania.

Wyświetlanie stosu wywołań pojedynczego wątku

  1. W menu Debuguj wskaż pozycję Windows, a następnie wybierz pozycję Wątki. Zadokuj okno Wątki w dolnej części programu Visual Studio.

  2. W menu Debuguj wskaż pozycję Windows, a następnie wybierz pozycję Stos wywołań. Zadokuj okno stosu wywołań w dolnej części programu Visual Studio.

  3. Kliknij dwukrotnie wątek w oknie Wątki , aby ustawić go jako bieżący. Bieżące wątki mają żółtą strzałkę. Po zmianie bieżącego wątku jego stos wywołań jest wyświetlany w oknie Stos wywołań.

Sprawdzanie okna stosów równoległych

W menu Debuguj wskaż pozycję Windows, a następnie wybierz pozycję Stosy równoległe. Upewnij się, że w polu w lewym górnym rogu zaznaczono pozycję Wątki .

Korzystając z okna Stosy równoległe , można jednocześnie wyświetlać wiele stosów wywołań w jednym widoku. Poniższa ilustracja przedstawia okno Stosy równoległe powyżej okna stosu wywołań.

Screenshot of Threads view in Parallel Stacks window.

Threads view in Parallel Stacks window

Stos wywołań wątku głównego pojawia się w jednym polu, a stosy wywołań dla pozostałych czterech wątków są zgrupowane w innym polu. Cztery wątki są grupowane razem, ponieważ ich ramki stosu współużytkują te same konteksty metody; oznacza to, że znajdują się one w tych samych metodach: A, Bi C. Aby wyświetlić identyfikatory wątków i nazwy wątków, które współużytkują to samo pole, umieść kursor nad polem z nagłówkiem ([#] Wątki). Bieżący wątek jest wyświetlany pogrubioną czcionką.

Screenshot of Tooltip that shows thread IDs and names.

Tooltip that shows thread IDs and names

Żółta strzałka wskazuje aktywną ramkę stosu bieżącego wątku.

Możesz ustawić, ile szczegółów ma być wyświetlanych dla ramek stosu (nazwy modułów, typy parametrów, nazwy parametrów, wartości parametrów, numery wierszy i przesunięcia bajtów), klikając prawym przyciskiem myszy w oknie Stos wywołań.

Niebieskie wyróżnienie wokół pola oznacza, że bieżący wątek jest częścią tego pola. Bieżący wątek jest również wskazywany przez pogrubioną ramkę stosu w etykietce narzędzia. Jeśli dwukrotnie klikniesz wątek Główny w oknie Wątki, możesz zobaczyć, że strzałka wyróżnienia w oknie Stosy równoległe jest odpowiednio przesuwana.

Screenshot of Highlighted main thread in Parallel Stacks window.

Highlighted main thread in Parallel Stacks window

Wznów wykonywanie do drugiego punktu przerwania

Aby wznowić wykonywanie do momentu osiągnięcia drugiego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj. Poniższa ilustracja przedstawia drzewo wątków w drugim punkcie przerwania.

Screenshot of Parallel Stacks window that shows many branches.

Parallel Stacks window that shows many branches

W pierwszym punkcie przerwania wszystkie wątki przeszły od S.A do S.B do metod S.C. Te informacje są nadal widoczne w oknie Stosy równoległe , ale cztery wątki zostały jeszcze bardziej przedstawione. Jeden z nich kontynuował S.D, a następnie S.E. Inny nadal S.F, S.G i S.H. Dwa inne nadal S.I i S.J, a stamtąd jeden z nich udał się do S.K, a drugi nadal nieużytkownika Kod zewnętrzny.

Możesz umieścić kursor nad ramkami stosu, aby wyświetlić identyfikatory wątków oraz inne szczegóły ramki. Niebieskie wyróżnienie wskazuje bieżący wątek, a żółta strzałka wskazuje aktywną ramkę stosu bieżącego wątku.

Możesz umieścić wskaźnik myszy na nagłówku pola, na przykład 1 wątek lub 2 wątki, aby wyświetlić identyfikatory wątków wątków. Możesz umieścić kursor nad ramkami stosu, aby wyświetlić identyfikatory wątków oraz inne szczegóły ramki. Niebieskie wyróżnienie wskazuje bieżący wątek, a żółta strzałka wskazuje aktywną ramkę stosu bieżącego wątku.

Ikona wątków tkaniny (przeplatane linie) wskazuje aktywne ramki stosu niebieżnych wątków. W oknie Stos wywołań kliknij dwukrotnie S.B, aby przełączyć ramki. Okno Stosy równoległe wskazuje bieżącą ramkę stosu bieżącego wątku przy użyciu ikony strzałki zakrzywionej.

Uwaga

Opis wszystkich ikon w oknie Stosy równoległe można znaleźć w oknie Korzystanie z stosów równoległych.

W oknie Wątki przełącz się między wątkami i sprawdź, czy widok w oknie Stosy równoległe jest aktualizowany.

Możesz przełączyć się na inny wątek lub do innej ramki innego wątku, korzystając z menu skrótów w oknie Stosy równoległe . Na przykład kliknij prawym przyciskiem myszy pozycję S.J, wskaż polecenie Przełącz na ramkę, a następnie wybierz polecenie.

Screenshot of Parallel Stacks Path of Execution.

Parallel Stacks Path of Execution

Kliknij prawym przyciskiem myszy pozycję S.C i wskaż polecenie Przełącz na ramkę. Jedno z poleceń ma znacznik wyboru wskazujący ramkę stosu bieżącego wątku. Możesz przełączyć się na tę ramkę tego samego wątku (tylko zakrzywione strzałki) lub przełączyć się na inny wątek (niebieskie wyróżnienie również przenosi). Na poniższej ilustracji przedstawiono podmenu.

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

Stacks menu with 2 options on C while J is current

Gdy kontekst metody jest skojarzony z tylko jedną ramką stosu, nagłówek pola wyświetla 1 wątek i można go przełączyć, klikając dwukrotnie. Jeśli dwukrotnie klikniesz kontekst metody, który ma skojarzona z nim więcej niż 1 ramkę, menu zostanie automatycznie wyświetlone. Po umieszczeniu wskaźnika myszy na kontekstach metody zwróć uwagę na czarny trójkąt po prawej stronie. Kliknięcie tego trójkąta powoduje również wyświetlenie menu skrótów.

W przypadku dużych aplikacji, które mają wiele wątków, warto skupić się tylko na podzestawie wątków. Okno Stosy równoległe może wyświetlać stosy wywołań tylko dla oflagowanych wątków. Aby flagować wątki, użyj menu skrótów lub pierwszej komórki wątku.

Na pasku narzędzi wybierz przycisk Pokaż tylko oflagowane obok pola listy.

Screenshot of Parallel Stacks window and tooltip.

Parallel Stacks window and tooltip

Teraz w oknie Stosy równoległe są wyświetlane tylko oflagowane wątki .

Wznów wykonywanie do trzeciego punktu przerwania

  1. Aby wznowić wykonywanie do momentu osiągnięcia trzeciego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj.

    Gdy wiele wątków znajduje się w tej samej metodzie, ale metoda nie była na początku stosu wywołań, metoda jest wyświetlana w różnych polach. Przykładem bieżącego punktu przerwania jest S.L, który zawiera trzy wątki i pojawia się w trzech polach. Kliknij dwukrotnie S.L.

    Screenshot of Execution path in Parallel Stacks window.

    Execution path in Parallel Stacks window

    Zwróć uwagę, że S.L jest pogrubiona w dwóch pozostałych polach, aby zobaczyć, gdzie jeszcze się pojawia. Jeśli chcesz zobaczyć, które ramki wywołają do S.L i które ramki wywołuje, wybierz przycisk Przełącz widok metody na pasku narzędzi. Na poniższej ilustracji przedstawiono widok metody okna Stosy równoległe .

    Screenshot of Method view in Parallel Stacks window.

    Method view in Parallel Stacks window

    Zwróć uwagę, że diagram jest przestawiany na wybranej metodzie i umieścił go we własnym polu w środku widoku. Wywołania i wywołania są wyświetlane odpowiednio u góry i dołu. Wybierz ponownie przycisk Przełącz widok metody, aby opuścić ten tryb.

    Menu skrótów w oknie Stosy równoległe zawiera również następujące inne elementy.

    • Ekran szesnastkowy przełącza liczby w etykietkach narzędzi między liczbami dziesiętnych i szesnastkowymi.

    • Symbol Ustawienia otworzyć odpowiednie okna dialogowe.

    • Pokaż wątki w źródle przełącza wyświetlanie znaczników wątku w kodzie źródłowym, co pokazuje lokalizację wątków w kodzie źródłowym.

    • Pokaż kod zewnętrzny wyświetla wszystkie ramki, nawet jeśli nie są w kodzie użytkownika. Spróbuj zobaczyć rozszerzenie diagramu, aby uwzględnić inne ramki (które mogą być wygaszone, ponieważ nie masz symboli dla nich).

  2. W oknie Stosy równoległe upewnij się, że na pasku narzędzi znajduje się przycisk Automatycznie przewijaj do bieżącej ramki stosu.

    Jeśli masz duże diagramy i przejdź do następnego punktu przerwania, możesz chcieć, aby widok automatycznie przewijał się do aktywnej ramki stosu bieżącego wątku; oznacza to, że wątek, który najpierw osiąga punkt przerwania.

  3. Zanim przejdziesz dalej, w oknie Stosy równoległe przewiń w lewo i aż w dół.

Wznów wykonywanie do czwartego punktu przerwania

  1. Aby wznowić wykonywanie do momentu osiągnięcia czwartego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj.

    Zwróć uwagę, jak widok jest automatycznie wyrejestrowany. Przełącz wątki w oknie Wątki lub przełącz ramki stosu w oknie stosu wywołań i zwróć uwagę, jak widok zawsze automatycznie wyrejestruje się do poprawnej ramki. Wyłącz opcję Automatyczne przewijanie do bieżącej ramki narzędzi i wyświetl różnicę.

    Widok Bird's Eye pomaga również w przypadku dużych diagramów w oknie Stosy równoległe . Domyślnie widok Bird's Eye View jest włączony. Można go jednak przełączyć, klikając przycisk między paskami przewijania w prawym dolnym rogu okna, jak pokazano na poniższej ilustracji.

    Screenshot of Birds eye view in Parallel Stacks window.

    Bird's-eye view in Parallel Stacks window

    W widoku lotu ptaka można szybko przesuwać prostokąt, aby szybko przesuwać się wokół diagramu.

    Innym sposobem przeniesienia diagramu w dowolnym kierunku jest wybranie pustego obszaru diagramu i przeciągnięcie go tam, gdzie chcesz.

    Aby powiększyć i z diagramu, naciśnij i przytrzymaj klawisze CTRL podczas przenoszenia kółka myszy. Możesz też wybrać przycisk Powiększenie na pasku narzędzi, a następnie użyć narzędzia Zoom.

    Możesz również wyświetlić stosy w kierunku od góry do dołu zamiast do dołu, klikając menu Narzędzia, klikając pozycję Opcje, a następnie wybierając lub wyczyść opcję w węźle Debugowanie.

  2. Przed kontynuowaniem w menu Debugowanie wybierz pozycję Zatrzymaj debugowanie , aby zakończyć wykonywanie.

Korzystanie z okna Zadań równoległych i widoku zadań okna stosów równoległych

Zalecamy wykonanie wcześniejszych procedur przed kontynuowaniem.

Uruchom ponownie aplikację, aż zostanie osiągnięty pierwszy punkt przerwania:

  1. W menu Debugowanie wybierz pozycję Rozpocznij debugowanie i poczekaj na trafienie pierwszego punktu przerwania.

  2. W menu Debuguj wskaż pozycję Windows, a następnie wybierz pozycję Wątki. Zadokuj okno Wątki w dolnej części programu Visual Studio.

  3. W menu Debuguj wskaż pozycję Windows i wybierz pozycję Stos wywołań. Zadokuj okno stosu wywołań w dolnej części programu Visual Studio.

  4. Kliknij dwukrotnie wątek w oknie Wątki , aby ustawić go jako bieżący. Bieżące wątki mają żółtą strzałkę. Po zmianie bieżącego wątku pozostałe okna zostaną zaktualizowane. Następnie przeanalizujemy zadania.

  5. W menu Debugowanie wskaż pozycję Windows, a następnie wybierz pozycję Zadania. Poniższa ilustracja przedstawia okno Zadania .

    Screenshot of Four running tasks in Tasks window.

    Four running tasks in Tasks window

    Dla każdego uruchomionego zadania można odczytać jego identyfikator, który jest zwracany przez tę samą właściwość o tej samej nazwie, identyfikator i nazwę wątku, który go uruchamia, jego lokalizację (umieść kursor nad, który wyświetla etykietkę narzędzia, która ma cały stos wywołań). Ponadto w kolumnie Task (Zadanie ) można zobaczyć metodę, która została przekazana do zadania. Innymi słowy, punktem początkowym.

    Możesz sortować dowolną kolumnę. Zwróć uwagę na glyph sortowania, który wskazuje kolumnę sortowania i kierunek. Możesz również zmienić kolejność kolumn, przeciągając je w lewo lub w prawo.

    Żółta strzałka wskazuje bieżące zadanie. Zadania można przełączać, klikając dwukrotnie zadanie lub używając menu skrótów. Po przełączeniu zadań podstawowy wątek staje się aktualny, a pozostałe okna zostaną zaktualizowane.

    Po ręcznym przełączeniu się z jednego zadania do innego kontur strzałki wskazuje bieżący kontekst debugera dla zadania niebieżnego.

    Gdy ręcznie przełączysz się z jednego zadania na inne, żółta strzałka przesuwa się, ale biała strzałka nadal wyświetla zadanie, które spowodowało przerwanie debugera.

Wznów wykonywanie do drugiego punktu przerwania

Aby wznowić wykonywanie do momentu osiągnięcia drugiego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj.

Wcześniej kolumna Stan pokazała wszystkie zadania jako Aktywne, ale teraz dwa zadania są zablokowane. Zadania mogą być blokowane z wielu różnych powodów. W kolumnie Stan umieść wskaźnik myszy na zadaniu oczekujących, aby dowiedzieć się, dlaczego jest on zablokowany. Na przykład na poniższej ilustracji zadanie 11 oczekuje na zadanie 12.

Screenshot of Two waiting tasks in Tasks window.

Wcześniej kolumna Stan pokazała wszystkie zadania jako Aktywne, ale teraz dwa zadania są zablokowane. Zadania mogą być blokowane z wielu różnych powodów. W kolumnie Stan umieść wskaźnik myszy na zadaniu oczekujących, aby dowiedzieć się, dlaczego jest on zablokowany. Na przykład na poniższej ilustracji zadanie 4 oczekuje na zadanie 5.

Two waiting tasks in Tasks window

Zadanie 4 z kolei czeka na monitor należący do wątku przydzielonego do zadania 2. (Kliknij prawym przyciskiem myszy wiersz nagłówka i wybierz polecenie Przypisania wątku kolumn>, aby wyświetlić wartość przypisania wątku dla zadania 2).

Waiting task and tooltip in Tasks window

Zadanie można flagować, klikając flagę w pierwszej kolumnie okna Zadania .

Za pomocą flagowania można śledzić zadania między różnymi punktami przerwania w tej samej sesji debugowania lub filtrować pod kątem zadań, których stosy wywołań są wyświetlane w oknie Stosy równoległe .

W przypadku wcześniejszego użycia okna Stosy równoległe były wyświetlane wątki aplikacji. Ponownie wyświetl okno stosów równoległych, ale tym razem wyświetl zadania aplikacji. W tym celu wybierz pozycję Zadania w polu w lewym górnym rogu. Na poniższej ilustracji przedstawiono widok zadań.

Screenshot of Tasks view in Parallel Stacks window.

Tasks view in Parallel Stacks window

Wątki, które obecnie nie wykonują zadań, nie są wyświetlane w widoku zadań okna Stosy równoległe . Ponadto w przypadku wątków, które wykonują zadania, niektóre ramki stosu, które nie są istotne dla zadań, są filtrowane od góry i dolnej części stosu.

Ponownie wyświetl okno Zadania. Kliknij prawym przyciskiem myszy dowolny nagłówek kolumny, aby wyświetlić menu skrótów dla kolumny.

Możesz użyć menu skrótów, aby dodać lub usunąć kolumny. Na przykład kolumna AppDomain nie jest zaznaczona; w związku z tym nie jest on wyświetlany na liście. Wybierz pozycję Nadrzędny. Kolumna Nadrzędna jest wyświetlana bez wartości dla żadnego z czterech zadań.

Wznów wykonywanie do trzeciego punktu przerwania

Aby wznowić wykonywanie do momentu osiągnięcia trzeciego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj.

Screenshot of Parent-child view in Tasks window.

W tym przykładzie uruchom polecenie , zwróć uwagę, że zadanie 11 i zadanie 12 są uruchomione w tym samym wątku (pokaż kolumnę Przypisania wątku, jeśli jest ukryta). Te informacje nie są wyświetlane w oknie Wątki . Zobaczysz, że jest to kolejna zaleta okna Zadania . Aby to potwierdzić, wyświetl okno Stosy równoległe . Upewnij się, że wyświetlasz zadania. Zadania 11 i 12 można zlokalizować, przeskanując etykietki narzędzi w oknie Stosy równoległe .

Task view in Parallel Stacks window

Nowe zadanie, zadanie 5, jest teraz uruchomione, a zadanie 4 czeka. Możesz zobaczyć, dlaczego umieszczając wskaźnik myszy na oczekujących zadaniach w oknie Stan . W kolumnie Nadrzędne zwróć uwagę, że zadanie 4 jest elementem nadrzędnym zadania 5.

Aby lepiej zwizualizować relację nadrzędny-podrzędny, kliknij prawym przyciskiem myszy wiersz nagłówka kolumny, a następnie wybierz pozycję Nadrzędny widok podrzędny. Powinna zostać wyświetlona poniższa ilustracja.

Parent-child view in Tasks window

Zwróć uwagę, że zadanie 4 i zadanie 5 są uruchomione w tym samym wątku (pokaż kolumnę Przypisania wątku, jeśli jest ukryta). Te informacje nie są wyświetlane w oknie Wątki . Zobaczysz, że jest to kolejna zaleta okna Zadania . Aby to potwierdzić, wyświetl okno Stosy równoległe . Upewnij się, że wyświetlasz zadania. Znajdź zadania 4 i 5, klikając je dwukrotnie w oknie Zadania . Po zakończeniu zostanie zaktualizowane niebieskie wyróżnienie w oknie Stosy równoległe . Zadania 4 i 5 można również zlokalizować, przeskanując etykietki narzędzi w oknie Stosy równoległe .

Task view in Parallel Stacks window

W oknie Stosy równoległe kliknij prawym przyciskiem myszy pozycję S.P, a następnie wybierz polecenie Przejdź do wątku. Okno przełącza się na Widok wątków, a odpowiednia ramka jest w widoku. Oba zadania można wyświetlić w tym samym wątku.

Highlighted thread in threads view

Jest to kolejna zaleta widoku zadań w oknie Stosy równoległe w porównaniu z oknem Wątki.

Wznów wykonywanie do czwartego punktu przerwania

Aby wznowić wykonywanie do momentu osiągnięcia trzeciego punktu przerwania, w menu Debugowanie wybierz pozycję Kontynuuj. Wybierz nagłówek kolumny ID, aby posortować według identyfikatora. Powinna zostać wyświetlona poniższa ilustracja.

Screenshot of Four task states in Parallel Stacks window.

Zadanie 10 i zadanie 11 czekają teraz na siebie i są blokowane. Istnieje również kilka nowych zadań, które są teraz zaplanowane. Zaplanowane zadania to zadania, które zostały uruchomione w kodzie, ale nie zostały jeszcze uruchomione. W związku z tym kolumny Lokalizacja i Przypisanie wątku pokazują komunikaty domyślne lub są puste.

Four task states in Parallel Stacks window

Ponieważ zadanie 5 zostało ukończone, nie jest już wyświetlane. Jeśli tak nie jest na komputerze, a impas nie jest wyświetlany, krok po raz, naciskając klawisz F11.

Zadanie 3 i zadanie 4 czekają teraz na siebie i są blokowane. Istnieją również 5 nowych zadań podrzędnych zadania podrzędnych 2 i są teraz zaplanowane. Zaplanowane zadania to zadania, które zostały uruchomione w kodzie, ale nie zostały jeszcze uruchomione. W związku z tym kolumny Lokalizacja i Przypisanie wątku są puste.

Ponownie wyświetl okno Stosy równoległe. Nagłówek każdego pola zawiera etykietkę narzędzia zawierającą identyfikatory i nazwy wątków. Przejdź do widoku Zadań w oknie Stosy równoległe . Umieść kursor na nagłówku, aby wyświetlić identyfikator i nazwę zadania oraz stan zadania, jak pokazano na poniższej ilustracji.

Header tooltip in Parallel Stacks window

Zadania można grupować według kolumny. W oknie Zadania kliknij prawym przyciskiem myszy nagłówek kolumny Stan, a następnie wybierz polecenie Grupuj według stanu. Poniższa ilustracja przedstawia okno Zadania pogrupowane według stanu.

Screenshot of Grouped tasks in Tasks window.

Grouped tasks in Tasks window

Możesz również grupować według dowolnej innej kolumny. Grupując zadania, można skoncentrować się na podzestawie zadań. Każda zwijana grupa ma liczbę elementów zgrupowanych razem.

Ostatnią funkcją okna Zadania do zbadania jest menu skrótów wyświetlane po kliknięciu zadania prawym przyciskiem myszy.

Menu skrótów wyświetla różne polecenia w zależności od stanu zadania. Polecenia mogą obejmować kopiowanie, wybieranie wszystkich, wyświetlanie szesnastkowe, przełączenie do zadania, zablokowanie przypisanego wątku, zablokowanie wszystkich wątków, ale to i odmrożenie przypisanego wątku i flagi.

Możesz zablokować podstawowy wątek zadania lub zadania albo zablokować wszystkie wątki z wyjątkiem przypisanego. Zamrożony wątek jest reprezentowany w oknie Zadania , ponieważ znajduje się w oknie Wątki za pomocą niebieskiej ikony wstrzymania .

Podsumowanie

W tym przewodniku przedstawiono okna debugera równoległych zadań i stosów równoległych . Użyj tych okien w rzeczywistych projektach, które używają kodu wielowątkowego. Możesz zbadać kod równoległy napisany w języku C++, C# lub Visual Basic.