Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym samouczku pokazano, jak debugować aplikację wielowątkową za pomocą widoku Wątki okien stosów równoległych . To okno pomaga zrozumieć i zweryfikować zachowanie w czasie wykonywania kodu wielowątkowego.
Widok Wątki jest obsługiwany dla języków C#, C++i Visual Basic. Przykładowy kod jest dostarczany dla języków C# i C++, ale niektóre odwołania do kodu i ilustracje mają zastosowanie tylko do przykładowego kodu w języku C#.
Widok Wątków ułatwia:
Wyświetl wizualizacje stosu wywołań dla wielu wątków, które zapewniają bardziej pełny obraz stanu aplikacji niż okno stosu wywołań, które pokazuje stos wywołań dla bieżącego wątku.
Pomoc w identyfikowaniu problemów, takich jak zablokowane lub zakleszczone wątki.
Wielowątkowane stosy wywołań
Identyczne sekcje stosu wywołań są grupowane razem, aby uprościć wizualizację złożonych aplikacji.
Poniższa animacja koncepcyjna pokazuje, jak stosuje się grupowanie do stosów wywołań. Grupowane są tylko identyczne segmenty stosu wywołań. Umieść kursor na zgrupowanym stosie wywołań, aby idenitfy wątków.
Omówienie przykładowego kodu (C#, C++)
Przykładowy kod w tym przewodniku dotyczy aplikacji, która symuluje dzień w życiu goryla. Celem ćwiczenia jest zrozumienie, jak używać widoku Wątki w oknie stosów równoległych do debugowania aplikacji wielowątkowej.
Przykład zawiera przykład zakleszczenia, który występuje, gdy na siebie czekają dwa wątki.
Aby stos wywołań był intuicyjny, przykładowa aplikacja wykonuje następujące kroki sekwencyjne:
- Tworzy obiekt reprezentujący goryl.
- Gorilla budzi się.
- Gorilla idzie na poranny spacer.
- Gorilla znajduje banany w dżungli.
- Gorilla zjada.
- Gorilla angażuje się w biznes małpy.
Tworzenie przykładowego projektu
Aby utworzyć projekt:
Otwórz program Visual Studio i utwórz nowy projekt.
Jeśli okno startowe nie jest otwarte, wybierz Plik>Okno startowe.
W oknie Start wybierz pozycję Nowy projekt.
W oknie Tworzenie nowego projektu wprowadź lub wpisz konsolę w polu wyszukiwania. Następnie wybierz pozycję C# lub C++ 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 wybranego języka, a następnie wybierz przycisk Dalej.
Note
Jeśli nie widzisz poprawnego szablonu, przejdź do pozycji NarzędziaPobierz narzędzia >i funkcje..., co spowoduje otwarcie Instalatora programu Visual Studio. Wybierz obciążenie programowanie aplikacji .NET na komputerach stacjonarnych, a następnie wybierz Modyfikuj.
W oknie Konfigurowanie nowego projektu wpisz nazwę lub użyj nazwy domyślnej w polu Nazwa projektu . Następnie wybierz pozycję Dalej.
W przypadku projektu platformy .NET wybierz zalecaną strukturę docelową lub platformę .NET 8, a następnie wybierz pozycję Utwórz.
Pojawia się nowy projekt konsoli. Po utworzeniu projektu zostanie wyświetlony plik źródłowy.
Otwórz plik kodu .cs (lub .cpp) w projekcie. Usuń jego zawartość, aby utworzyć pusty plik kodu.
Wklej następujący kod wybranego języka do pustego pliku kodu.
using System.Diagnostics; namespace Multithreaded_Deadlock { class Jungle { public static readonly object tree = new object(); public static readonly object banana_bunch = new object(); public static Barrier barrier = new Barrier(2); public static int FindBananas() { // Lock tree first, then banana lock (tree) { lock (banana_bunch) { Console.WriteLine("Got bananas."); return 0; } } } static void Gorilla_Start(object lockOrderObj) { Debugger.Break(); bool lockTreeFirst = (bool)lockOrderObj; Gorilla koko = new Gorilla(lockTreeFirst); int result = 0; var done = new ManualResetEventSlim(false); Thread t = new Thread(() => { result = koko.WakeUp(); done.Set(); }); t.Start(); done.Wait(); } static void Main(string[] args) { List<Thread> threads = new List<Thread>(); // Start two threads with opposite lock orders threads.Add(new Thread(Gorilla_Start)); threads[0].Start(true); // First gorilla locks tree then banana threads.Add(new Thread(Gorilla_Start)); threads[1].Start(false); // Second gorilla locks banana then tree foreach (var t in threads) { t.Join(); } } } class Gorilla { private readonly bool lockTreeFirst; public Gorilla(bool lockTreeFirst) { this.lockTreeFirst = lockTreeFirst; } public int WakeUp() { int myResult = MorningWalk(); return myResult; } public int MorningWalk() { Debugger.Break(); if (lockTreeFirst) { lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } else { lock (Jungle.banana_bunch) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } return 0; } public void GobbleUpBananas() { Console.WriteLine("Trying to gobble up food..."); DoSomeMonkeyBusiness(); } public void DoSomeMonkeyBusiness() { Thread.Sleep(1000); Console.WriteLine("Monkey business done"); } } }W menu Plik wybierz pozycję Zapisz wszystko.
W menu Build wybierz pozycję Build Solution.
Użyj widoku Wątki w oknie stosów równoległych
Aby rozpocząć debugowanie:
W menu Debugowanie wybierz pozycję Rozpocznij debugowanie (lub F5) i poczekaj na pierwsze
Debugger.Break()trafienie.Note
W języku C++debuger wstrzymuje się w pliku
__debug_break(). Pozostałe odwołania do kodu i ilustracje w tym artykule dotyczą wersji języka C#, ale te same zasady debugowania dotyczą języka C++.Naciśnij F5 raz, a debuger ponownie wstrzymuje się w tym samym
Debugger.Break()wierszu.Spowoduje to wstrzymanie w drugim wywołaniu metody
Gorilla_Start, która występuje w drugim wątku.Tip
Debuger dzieli kod na każdy wątek. Na przykład oznacza to, że jeśli naciskasz F5, aby kontynuować wykonywanie, a aplikacja napotka następny punkt przerwania, może wejść w kod w innym wątku. Jeśli chcesz zarządzać tym zachowaniem w celach debugowania, możesz dodać dodatkowe punkty przerwania, warunkowe punkty przerwania lub użyć opcji Break All. Aby uzyskać więcej informacji na temat używania warunkowych punktów przerwania, zobacz Obserwowanie pojedynczego wątku z warunkowymi punktami przerwania.
Wybierz pozycję Debuguj > stosy równoległe systemu Windows>, aby otworzyć okno Stosy równoległe, a następnie wybierz pozycję Wątki z listy rozwijanej Widok w oknie.
W widoku wątków ramka stosu i ścieżka wywołania bieżącego wątku są zaznaczone na niebiesko. Bieżąca lokalizacja wątku jest wyświetlana za pomocą żółtej strzałki.
Zwróć uwagę, że etykieta stosu wywołań dla
Gorilla_Startto 2 Wątki. Po ostatnim naciśnięciu F5 rozpoczęto kolejny wątek. W celu uproszczenia złożonych aplikacji identyczne stosy wywołań są grupowane w jedną wizualną reprezentację. Upraszcza to potencjalnie złożone informacje, szczególnie w scenariuszach z wieloma wątkami.Podczas debugowania można przełączać, czy kod zewnętrzny jest wyświetlany. Aby przełączyć tę funkcję, zaznacz lub odznacz opcję Pokaż kod zewnętrzny. Jeśli pokażesz kod zewnętrzny, nadal możesz użyć tego przewodnika, ale wyniki mogą się różnić od ilustracji.
Naciśnij F5 ponownie, a debuger wstrzymuje się w
Debugger.Break()wierszu w metodzieMorningWalk.W oknie Stosy równoległe jest wyświetlana lokalizacja bieżącego wątku wykonującego w metodzie
MorningWalk.
Umieść kursor na metodzie
MorningWalk, aby uzyskać informacje o dwóch wątkach reprezentowanych przez zgrupowany stos wywołań.Bieżący wątek jest również wyświetlany na liście Wątki na pasku narzędzi Debugowanie.
Możesz użyć listy Wątki , aby przełączyć kontekst debugera na inny wątek. Nie powoduje to zmiany bieżącego wątku wykonywania, lecz wyłącznie kontekstu debugera.
Alternatywnie możesz przełączyć kontekst debugera, klikając dwukrotnie metodę w widoku Wątki lub klikając prawym przyciskiem myszy metodę w widoku Wątki i wybierając pozycję Przełącz na ramkę>[identyfikator wątku].
Naciśnij F5 ponownie, a debuger wstrzymuje metodę
MorningWalkdla drugiego wątku.
W zależności od czasu wykonywania wątku w tym momencie zobaczysz oddzielne lub zgrupowane stosy wywołań.
Na poprzedniej ilustracji stosy wywołań dla dwóch wątków są częściowo zgrupowane. Identyczne segmenty stosów wywołań są grupowane, a linie strzałek wskazują segmenty rozdzielone (czyli nie identyczne). Bieżąca ramka stosu jest oznaczona niebieskim podświetleniem.
Naciśnij ponownie F5, a zauważysz duże opóźnienie. W widoku wątków nie są wyświetlane żadne informacje o stosie wywołań.
Opóźnienie jest spowodowane zakleszczeniem. Nic nie jest wyświetlane w widoku Wątki, ponieważ mimo że wątki mogą być zablokowane, nie są obecnie wstrzymane w debugerze.
Note
W języku C++zostanie również wyświetlony błąd debugowania wskazujący, że
abort()został wywołany.Tip
Przycisk Przerwij wszystko jest dobrym sposobem na uzyskanie informacji o stosie wywołań, jeśli wystąpi zakleszczenie lub wszystkie wątki są obecnie zablokowane.
W górnej części środowiska IDE na pasku narzędzi Debugowanie wybierz przycisk Przerwij wszystko (ikona wstrzymywania) lub naciśnij Ctrl + Alt + Break.
Górna część stosu wywołań w widoku Wątki pokazuje, że
FindBananasjest zakleszczone. Wskaźnik wykonywania w elemencieFindBananasjest zwiniętą zieloną strzałką, która wskazuje bieżący kontekst debugera. Ponadto informuje nas, że wątki nie są obecnie uruchomione.Note
W języku C++nie widzisz przydatnych informacji i ikon "zakleszczenia". Jednak nadal znajdziesz zwiniętą zieloną strzałkę w
Jungle.FindBananaspliku , wskazując na lokalizację zakleszczenia.W edytorze kodu znajdujemy zwiniętą zieloną strzałkę w
lockfunkcji. Dwa wątki są blokowane na funkcjilockw metodzieFindBananas.W zależności od kolejności wykonywania wątków, zakleszczenie pojawia się w instrukcji
lock(tree)lublock(banana_bunch).Wywołanie metody
lockblokuje wątki w metodzieFindBananas. Jeden wątek czeka na zwolnienie blokadytreeprzez drugi wątek, ale drugi wątek czeka na zwolnienie blokadybanana_bunch, zanim będzie mógł zwolnić blokadę natree. Jest to przykład klasycznego zakleszczenia, który występuje, gdy dwa wątki czekają na siebie nawzajem.Jeśli używasz narzędzia Copilot, możesz również uzyskać podsumowania wątków generowanych przez sztuczną inteligencję, aby ułatwić zidentyfikowanie potencjalnych zakleszczeń.
Naprawianie przykładowego kodu
Aby naprawić ten kod, zawsze uzyskuj wiele blokad w spójnym, globalnym porządku we wszystkich wątkach. Zapobiega to kolistym oczekiwaniom i eliminuje zakleszczenia.
Aby naprawić zakleszczenie, zastąp kod kodem znajdującym się poniżej
MorningWalk.public int MorningWalk() { Debugger.Break(); // Always lock tree first, then banana_bunch lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // OK to remove lock (Jungle.banana_bunch) { Jungle.FindBananas(); GobbleUpBananas(); } } return 0; }Uruchom ponownie aplikację.
Summary
W tym poradniku pokazano okno debugera stosów równoległych. Użyj tego okna 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.