Pracuję z nową bazą kodu, która intensywnie wykorzystuje async / czekaj. Większość osób w moim zespole również jest całkiem nowa, aby asynchronizować / oczekiwać. Zwykle trzymamy się najlepszych praktyk określonych przez Microsoft , ale generalnie potrzebujemy naszego kontekstu, aby przepłynąć przez wywołanie asynchroniczne i pracujemy z bibliotekami, które tego nie robią ConfigureAwait(false)
.
Połącz te wszystkie rzeczy i wpadniemy w impas asynchroniczny opisany w artykule ... co tydzień. Nie pojawiają się podczas testów jednostkowych, ponieważ nasze kpiące źródła danych (zwykle poprzez Task.FromResult
) nie wystarczą, aby wywołać impas. Dlatego podczas testów środowiska wykonawczego lub testów integracyjnych niektóre połączenia serwisowe wychodzą na lunch i nigdy nie wracają. To zabija serwery i generalnie psuje rzeczy.
Problem polega na tym, że śledzenie miejsca, w którym popełniono błąd (zwykle po prostu brak asynchronizacji aż do samego końca), zazwyczaj wymaga ręcznej kontroli kodu, co jest czasochłonne i nie można go zautomatyzować.
Jaki jest lepszy sposób zdiagnozowania przyczyny impasu?
async
artykułów tego faceta ?Odpowiedzi:
Ok - nie jestem pewien, czy poniższe informacje będą dla ciebie pomocne, ponieważ poczyniłem pewne założenia przy opracowywaniu rozwiązania, które może, ale nie musi być prawdziwe w twoim przypadku. Być może moje „rozwiązanie” jest zbyt teoretyczne i działa tylko na sztuczne przykłady - nie wykonałem żadnych testów poza tymi poniżej.
Ponadto widziałbym następujące obejście problemu zamiast prawdziwego rozwiązania, ale biorąc pod uwagę brak odpowiedzi, myślę, że może być ono lepsze niż nic (obserwowałem twoje pytanie w oczekiwaniu na rozwiązanie, ale nie widząc żadnego opublikowanego, zacząłem grać wokół problemu).
Ale dość powiedziane: powiedzmy, że mamy prostą usługę danych, której można użyć do pobrania liczby całkowitej:
Prosta implementacja wykorzystuje kod asynchroniczny:
Teraz pojawia się problem, jeśli używamy kodu „niepoprawnie”, jak pokazano w tej klasie.
Foo
nieprawidłowo dostępTask.Result
zamiastawait
ing wynik jakBar
robi:To, czego teraz potrzebujemy, to sposób na napisanie testu, który się powiedzie, gdy zadzwonisz,
Bar
ale nie zadzwoni podczas rozmowyFoo
(przynajmniej jeśli poprawnie zrozumiałem pytanie ;-)).Pozwolę kodowi mówić; oto co wymyśliłem (używając testów Visual Studio, ale powinno również działać przy użyciu NUnit):
DataServiceMock
wykorzystujeTaskCompletionSource<T>
. To pozwala nam ustawić wynik w określonym punkcie w trakcie testu, co prowadzi do następnego testu. Zauważ, że używamy delegata, aby przekazać TaskCompletionSource z powrotem do testu. Możesz także umieścić to w metodzie Initialize testu i użyć właściwości.To, co się tutaj dzieje, polega na tym, że najpierw sprawdzamy, czy możemy opuścić metodę bez blokowania (nie zadziałałoby, gdyby ktoś uzyskał dostęp
Task.Result
- w tym przypadku wystąpiłby limit czasu, ponieważ wynik zadania nie został udostępniony, dopóki nie zostanie zwrócona metoda ).Następnie ustawiamy wynik (teraz metoda może zostać wykonana) i weryfikujemy wynik (w teście jednostkowym możemy uzyskać dostęp do Task.Result, ponieważ tak naprawdę chcemy, aby nastąpiło blokowanie).
Kompletna klasa testowa -
BarTest
udana iFooTest
nieudana zgodnie z życzeniem.I mała klasa pomocnicza do testowania impasu / przekroczenia limitu czasu:
źródło
Oto strategia, której użyłem w ogromnej i bardzo, bardzo wielowątkowej aplikacji:
Po pierwsze, potrzebujesz struktury danych wokół muteksu (niestety) i nie twórz katalogu połączeń zsynchronizowanych. W tej strukturze danych znajduje się link do dowolnego wcześniej zablokowanego muteksu. Każdy muteks ma „poziom” rozpoczynający się od 0, który przypisujesz podczas tworzenia muteksu i nigdy się nie zmieni.
Zasada jest taka: Jeśli muteks jest zablokowany, możesz zablokować tylko inne muteksy na niższym poziomie. Jeśli zastosujesz się do tej zasady, nie będziesz mieć impasu. Gdy znajdziesz naruszenie, aplikacja nadal działa i działa poprawnie.
Gdy znajdziesz naruszenie, masz dwie możliwości: Być może źle przypisałeś poziomy. Zamknąłeś A, a następnie B, więc B powinien mieć niższy poziom. Więc popraw poziom i spróbuj ponownie.
Inna możliwość: nie można tego naprawić. Niektóre z twoich kodów blokują A, a następnie blokują B, a niektóre inne kody blokują B, a następnie blokują A. Nie ma możliwości przypisania poziomów, aby na to pozwolić. I oczywiście jest to potencjalny impas: jeśli oba kody działają jednocześnie w różnych wątkach, istnieje szansa na impas.
Po wprowadzeniu tej fazy była raczej krótka faza, w której poziomy musiały zostać dostosowane, a następnie dłuższa faza, w której znaleziono potencjalne impasy.
źródło
Czy używasz Async / Await, aby równolegle wykonywać drogie połączenia, np. Z bazą danych? W zależności od ścieżki wykonania w bazie danych może to nie być możliwe.
Pokrycie testowe za pomocą asynchronizacji / oczekiwania może być trudne i nie ma nic lepszego niż rzeczywiste wykorzystanie produkcji do znalezienia błędów. Jeden wzorzec, który możesz wziąć pod uwagę, to przekazywanie identyfikatora korelacji i rejestrowanie go w stosie, a następnie zastosowanie kaskadowego limitu czasu rejestrującego błąd. Jest to bardziej wzorzec SOA, ale przynajmniej dałby ci pojęcie, skąd pochodzi. Użyliśmy tego z Splunk, aby znaleźć zakleszczenia.
źródło