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ć asynchroniczną aplikację w języku C#, korzystając z widoku Zadań w oknie Stosów równoległych. To okno pomaga zrozumieć i zweryfikować zachowanie kodu w czasie wykonywania, który używa wzorca asynchronicznego/await, nazywanego również wzorcem asynchronicznym opartym na zadaniach (TAP).
W przypadku aplikacji korzystających z biblioteki równoległej zadań (TPL), ale nie wzorca asynchronicznego/await lub dla aplikacji języka C++ używających środowiska uruchomieniowego współbieżności, użyj widoku Wątki w oknie Stosy równoległe do debugowania. Aby uzyskać więcej informacji, zobacz Debugowanie zakleszczenia i Wyświetlanie wątków i zadań w oknie Stosy równoległe.
Widok Zadania ułatwia wykonywanie następujących czynności:
Wyświetlanie wizualizacji stosu wywołań dla aplikacji korzystających ze wzorca async/await. W tych scenariuszach widok Zadania zawiera bardziej pełny obraz stanu aplikacji.
Zidentyfikuj kod asynchroniczny, który ma zostać uruchomiony, ale nie jest jeszcze uruchomiony. Na przykład żądanie HTTP, które nie zwróciło żadnych danych, jest bardziej prawdopodobne, aby było wyświetlane w widoku Zadania zamiast widoku Wątki, co pomaga wyizolować problem.
Pomoc w identyfikowaniu problemów, takich jak wzorzec synchronizacji nad asynchronicznością, wraz ze wskazówkami dotyczącymi potencjalnych problemów, takich jak zablokowane lub oczekujące zadania. Wzorzec kodu synchroniczno-asynchronicznego odnosi się do kodu, który wywołuje metody asynchroniczne w sposób synchroniczny, co jest znane z blokowania wątków i jest najczęstszą przyczyną głodu zasobów puli wątków.
Stosy wywołań asynchronicznych
Widok Zadań w Stosach Równoległych zawiera wizualizację stosów wywołań asynchronicznych, dzięki czemu możesz zobaczyć, co się dzieje (lub powinno się zdarzyć) w aplikacji.
Poniżej przedstawiono kilka ważnych kwestii, które należy zapamiętać podczas interpretowania danych w widoku Zadania.
Stosy wywołań asynchronicznych to logiczne lub wirtualne stosy wywołań, a nie fizyczne stosy wywołań reprezentujące stos. Podczas pracy z kodem asynchronicznym (na przykład przy użyciu słowa kluczowego
await) debuger udostępnia widok "asynchronicznych stosów wywołań" lub "wirtualnych stosów wywołań". Stosy wywołań asynchronicznych różnią się od stosów wywołań opartych na wątkach lub "stosów fizycznych", ponieważ stosy wywołań asynchronicznych nie muszą być aktualnie uruchomione w żadnym wątku fizycznym. Zamiast tego stosy wywołań asynchronicznych są kontynuacjami lub "obietnicami" kodu, który będzie uruchamiany w przyszłości asynchronicznie. Stosy wywołań są tworzone przy użyciu kontynuacji.Kod asynchroniczny, który jest zaplanowany, ale nie jest obecnie uruchomiony, nie jest wyświetlany w fizycznym stosie wywołań, ale powinien zostać wyświetlony w stosie wywołań asynchronicznych w widoku Zadania. Jeśli blokujesz wątki przy użyciu metod takich jak
.Waitlub.Result, możesz zobaczyć kod w stosie wywołań fizycznych.Asynchroniczne stosy wywołań wirtualnych nie zawsze są intuicyjne ze względu na rozgałęzianie, które wynika z użycia wywołań metod, takich jak
.WaitAnylub.WaitAll.Okno Stos wywołań może być przydatne w połączeniu z widokiem Zadania, ponieważ pokazuje fizyczny stos wywołań dla bieżącego wątku wykonywania.
Identyczne sekcje stosu wywołań wirtualnych są grupowane razem, aby uprościć wizualizację złożonych aplikacji.
Poniższa animacja koncepcyjna pokazuje, jak grupowanie jest stosowane do wirtualnych stosów wywołań. Grupowane są tylko identyczne segmenty wirtualnego stosu wywołań. Umieść kursor na zgrupowanym stosie wywołań, aby zidentyfikować wątki uruchamiające zadania.
Przykład w języku C#
Przykładowy kod w tym przewodniku dotyczy aplikacji, która symuluje dzień w życiu goryla. Celem ćwiczenia jest zrozumienie, jak korzystać z widoku Zadań w oknie Stosów równoległych do debugowania aplikacji asynchronicznej.
Przykład obejmuje zastosowanie antywzorca synchronizacji na asynchronicznych operacjach, co może skutkować wyczerpaniem puli wątków.
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
Otwórz program Visual Studio i utwórz nowy projekt.
Jeśli okno startowe nie jest otwarte, wybierz Plik>Okno startowe.
W oknie startowym wybierz pozycję Nowy projekt.
W oknie Tworzenie nowego projektu wprowadź lub wpisz konsolę w polu wyszukiwania. Następnie wybierz pozycję 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 platformy .NET, a następnie wybierz przycisk Dalej.
Uwaga / Notatka
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 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 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 AsyncTasks_SyncOverAsync { class Jungle { public static async Task<int> FindBananas() { await Task.Delay(1000); Console.WriteLine("Got bananas."); return 0; } static async Task Gorilla_Start() { Debugger.Break(); Gorilla koko = new Gorilla(); int result = await Task.Run(koko.WakeUp); } static async Task Main(string[] args) { List<Task> tasks = new List<Task>(); for (int i = 0; i < 2; i++) { Task task = Gorilla_Start(); tasks.Add(task); } await Task.WhenAll(tasks); } } class Gorilla { public async Task<int> WakeUp() { int myResult = await MorningWalk(); return myResult; } public async Task<int> MorningWalk() { int myResult = await Jungle.FindBananas(); GobbleUpBananas(myResult); return myResult; } /// <summary> /// Calls a .Wait. /// </summary> public void GobbleUpBananas(int food) { Console.WriteLine("Trying to gobble up food synchronously..."); Task mb = DoSomeMonkeyBusiness(); mb.Wait(); } public async Task DoSomeMonkeyBusiness() { Debugger.Break(); while (!System.Diagnostics.Debugger.IsAttached) { Thread.Sleep(100); } await Task.Delay(30000); Console.WriteLine("Monkey business done"); } } }Po zaktualizowaniu pliku kodu zapisz zmiany i skompiluj rozwiązanie.
W menu Plik wybierz pozycję Zapisz wszystko.
W menu Build wybierz pozycję Build Solution.
Użyj widoku zadań w oknie stosów równoległych
W menu Debugowanie wybierz pozycję Rozpocznij debugowanie (lub F5) i poczekaj na pierwsze
Debugger.Break()trafienie.Naciśnij F5 raz, a debuger ponownie wstrzymuje się w tym samym
Debugger.Break()wierszu.Nastąpi wstrzymanie w drugim wywołaniu metody
Gorilla_Start, które odbywa się w ramach drugiego zadania asynchronicznego.Wybierz pozycję Debuguj > stosy równoległe systemu Windows>, aby otworzyć okno Stosy równoległe, a następnie wybierz pozycję Zadania z listy rozwijanej Widok w oknie.
Zwróć uwagę, że etykiety stosów wywołań asynchronicznych opisują 2 asynchroniczne stosy logiczne. Po ostatnim naciśnięciu F5 uruchomiono kolejne zadanie. W celu uproszczenia złożonych aplikacji identyczne stosy wywołań asynchronicznych są grupowane w jedną wizualną reprezentację. Zapewnia to więcej pełnych informacji, szczególnie w scenariuszach z wieloma zadaniami.
W przeciwieństwie do widoku Zadania okno Stos wywołań pokazuje stos wywołań tylko dla bieżącego wątku, a nie dla wielu zadań. Często warto wyświetlić oba te elementy razem, aby uzyskać bardziej pełny obraz stanu aplikacji.
Wskazówka
W oknie Stos wywołań można wyświetlić informacje, takie jak zakleszczenie, przy użyciu opisu
Async cycle.Podczas debugowania można przełączać, czy kod zewnętrzny jest wyświetlany. Aby przełączyć tę funkcję, kliknij prawym przyciskiem myszy nagłówek tabeli Nazwa okna Stos wywołań, a następnie wybierz 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 metodę
DoSomeMonkeyBusiness.
Ten widok przedstawia bardziej kompletny stos wywołań asynchronicznych po dodaniu bardziej asynchronicznych metod do wewnętrznego łańcucha kontynuacji, który występuje w przypadku używania
awaiti podobnych metod.DoSomeMonkeyBusinessmoże być obecny lub nie na górze stosu wywołań asynchronicznych, ponieważ jest to metoda asynchroniczna, ale nie została jeszcze dodana do łańcucha kontynuacji. Dowiesz się, dlaczego tak jest w kolejnych krokach.W tym widoku wyświetlana jest również ikona stanu zablokowanego
Jungle.Main
. Jest to pouczające, ale zwykle nie wskazuje problemu. Zadanie jest blokowane, ponieważ oczekuje na zakończenie innego zadania, na sygnał zdarzenia lub na zwolnienie blokady.Umieść kursor nad metodą
GobbleUpBananas, aby uzyskać informacje o dwóch wątkach, które wykonują zadania.
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.
Naciśnij F5 ponownie, a debuger wstrzymuje metodę
DoSomeMonkeyBusinessdla drugiego zadania.
W zależności od czasu wykonania zadania w tym momencie zobaczysz oddzielne lub zgrupowane stosy wywołań asynchronicznych.
Na poprzedniej ilustracji stosy wywołań asynchronicznych dla dwóch zadań są oddzielne, ponieważ nie są identyczne.
Ponownie naciśnij F5 i zobaczysz duże opóźnienie, a w widoku Zadania nie są wyświetlane żadne informacje o stosie wywołań asynchronicznych.
Opóźnienie jest spowodowane długotrwałym zadaniem. Na potrzeby tego przykładu symuluje długotrwałe zadanie, takie jak żądanie internetowe, które może prowadzić do wyczerpania puli wątków. W widoku Zadania nie są wyświetlane żadne elementy, ponieważ mimo że zadania mogą być zablokowane, nie są obecnie wstrzymane w debugerze.
Wskazówka
Przycisk Przerwij wszystko to dobry sposób na uzyskanie informacji o stosie wywołań, jeśli występuje zakleszczenie lub wszystkie zadania i wątki są obecnie blokowane.
W górnej części środowiska IDE na pasku narzędzi Debugowanie wybierz przycisk Przerwij wszystko (ikona wstrzymywania), Ctrl + Alt + Break.
W górnej części stosu wywołań asynchronicznych w widoku zadań widzisz, że
GobbleUpBananasjest zablokowany. W rzeczywistości dwa zadania są blokowane w tym samym momencie. Zablokowane zadanie nie musi być nieoczekiwane i niekoniecznie oznacza, że występuje problem. Jednak zaobserwowane opóźnienie w wykonywaniu wskazuje na problem, a informacje o stosie wywołań pokazują w tym miejscu lokalizację problemu.Po lewej stronie na poprzednim zrzucie ekranu, zwinięta zielona strzałka wskazuje bieżący kontekst debugera. Dwa zadania są blokowane na
mb.Wait()w metodzieGobbleUpBananas.Okno Stos wywołań pokazuje również, że bieżący wątek jest zablokowany.
Wywołanie metody
Wait()blokuje wątki w ramach synchronicznego wywołania metodyGobbleUpBananas. Jest to przykład antywzorca 'synchronizacja przez asynchroniczność', i jeśli wystąpiłby w wątku interfejsu użytkownika lub w przypadku dużych obciążeń obliczeniowych, zwykle można by to rozwiązać za pomocą poprawki w kodzie przy użyciuawait. Aby uzyskać więcej informacji, zobacz Debugowanie zastoju puli wątków. pl-PL: Aby użyć narzędzi profilowania do debugowania wyczerpania puli wątków, zobacz Analiza przypadku: Izolowanie problemu z wydajnością.Co również jest interesujące,
DoSomeMonkeyBusinessnie pojawia się na stosie wywołań. Jest ona obecnie zaplanowana, nie jest uruchomiona, więc jest wyświetlana tylko w stosie wywołań asynchronicznych w widoku Zadania.Wskazówka
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 w celach debugowania, możesz dodać dodatkowe punkty przerwania, dodać warunkowe punkty przerwania lub użyć opcji Przerwij wszystko. Aby uzyskać więcej informacji na temat tego zachowania, zobacz Obserwowanie pojedynczego wątku z warunkowymi punktami przerwania.
Naprawianie przykładowego kodu
Zastąp metodę
GobbleUpBananasponiższym kodem.public async Task GobbleUpBananas(int food) // Previously returned void. { Console.WriteLine("Trying to gobble up food..."); //Task mb = DoSomeMonkeyBusiness(); //mb.Wait(); await DoSomeMonkeyBusiness(); }W metodzie
MorningWalkwywołaj GobbleUpBananas używającawait.await GobbleUpBananas(myResult);Wybierz przycisk Uruchom ponownie (Ctrl + Shift + F5), a następnie naciśnij F5 kilka razy, aż aplikacja wydaje się zawieszać.
Naciśnij przycisk Przerwij wszystko.
Tym razem
GobbleUpBananasprogram jest uruchamiany asynchronicznie. Po przerwaniu zobaczysz stos wywołań asynchronicznych.
Okno Stos wywołań jest puste z wyjątkiem
ExternalCodeelementu.Edytor kodu nie wyświetla żadnych elementów, z wyjątkiem komunikatu wskazującego, że wszystkie wątki wykonują kod zewnętrzny.
Jednak widok Zadania udostępnia przydatne informacje.
DoSomeMonkeyBusinessznajduje się w górnej części stosu wywołań asynchronicznych zgodnie z oczekiwaniami. To dokładnie wskazuje nam, gdzie znajduje się długotrwała metoda. Jest to pomocne przy izolowaniu problemów z async/await, gdy fizyczny stos wywołań w oknie stosu wywołań nie dostarcza wystarczającej ilości szczegółów.
Podsumowanie
W tym poradniku pokazano okno debugera stosów równoległych. Użyj tego okna w aplikacjach, które stosują wzorzec async/await.