Mam następujące cztery testy, a ostatni zawiesza się po uruchomieniu. Dlaczego to się dzieje:
[Test]
public void CheckOnceResultTest()
{
Assert.IsTrue(CheckStatus().Result);
}
[Test]
public async void CheckOnceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceResultTest()
{
Assert.IsTrue(CheckStatus().Result); // This hangs
Assert.IsTrue(await CheckStatus());
}
private async Task<bool> CheckStatus()
{
var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
IRestResponse<DummyServiceStatus> response = await restResponse;
return response.Data.SystemRunning;
}
Używam tej metody rozszerzenia dla restsharp RestClient :
public static class RestClientExt
{
public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
{
var tcs = new TaskCompletionSource<IRestResponse<T>>();
RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
return tcs.Task;
}
}
public class DummyServiceStatus
{
public string Message { get; set; }
public bool ValidVersion { get; set; }
public bool SystemRunning { get; set; }
public bool SkipPhrase { get; set; }
public long Timestamp { get; set; }
}
Dlaczego ostatni test się zawiesza?
async void
metod testów jednostkowych; po prostu nie będą działać. Jednak NUnit tak. Powiedział, że zgadzam się z ogólną zasadą preferującasync Task
nadasync void
.Odpowiedzi:
Napotkasz standardową sytuację zakleszczenia, którą opisuję na moim blogu i w artykule MSDN :
async
metoda próbuje zaplanować kontynuację w wątku, który jest blokowany przez wywołanieResult
.W tym przypadku Twój
SynchronizationContext
jest używany przez NUnit do wykonywaniaasync void
metod testowych.async Task
Zamiast tego spróbuję użyć metod testowych.źródło
async
do końca” (jak wspomniano w moim artykule MSDN). Innymi słowy - jak stwierdza tytuł mojego posta na blogu - „nie blokuj kodu asynchronicznego”.Wait()
metodą wywoływaniaasync
. Ale wydaje mi się, że to przesuwa problem na wyższy poziom. W pewnym momencie trzeba czymś zarządzać synchronicznie. Co się stanie, jeśli moja funkcja jest celowo synchroniczna, ponieważ zarządza długo działającymi wątkami roboczymiTask.Run()
? Jak mam czekać, aż to się skończy bez zakleszczenia w moim teście NUnit?At some point, something has to be managed synchronously.
- wcale. W przypadku aplikacji interfejsu użytkownika punktem wejścia może być programasync void
obsługi zdarzeń. W przypadku aplikacji serwerowych punktem wejścia może byćasync Task<T>
akcja. Zaleca się używanieasync
obu, aby uniknąć blokowania gwintów. Test NUnit może być synchroniczny lub asynchroniczny; jeśli async, zrób toasync Task
zamiastasync void
. Jeśli jest synchroniczny, nie powinien mieć znaku,SynchronizationContext
więc nie powinno być impasu.Uzyskanie wartości metodą asynchroniczną:
Synchroniczne wywoływanie metody asynchronicznej
Dzięki użyciu Task.Run nie wystąpią żadne problemy z zakleszczeniem.
źródło
async void
metod testów jednostkowych i usuwanie gwarancji tego samego wątku zapewnianych przezSynchronizationContext
testowany system..GetAwaiter().GetResult()
zamiast.Result
, aby żadenException
nie był opakowany.Możesz uniknąć zakleszczenia dodawania
ConfigureAwait(false)
do tej linii:=>
Opisałem tę pułapkę w moim wpisie na blogu Pułapki async / await
źródło
Blokujesz interfejs użytkownika za pomocą właściwości Task.Result. W dokumentacji MSDN wyraźnie wspomnieli, że
Najlepszym rozwiązaniem dla tego scenariusza byłoby usunięcie zarówno await, jak i async z metod i użycie tylko zadania, w którym zwracasz wynik. Nie zakłóci to sekwencji wykonywania.
źródło
Jeśli nie otrzymujesz żadnych wywołań zwrotnych lub kontrolka zawiesza się, po wywołaniu funkcji asynchronicznej usługi / API, musisz skonfigurować Context, aby zwracał wynik w tym samym wywołanym kontekście.
Posługiwać się
TestAsync().ConfigureAwait(continueOnCapturedContext: false);
Będziesz mieć do czynienia z tym problemem tylko w aplikacjach internetowych, ale nie w
static void main
.źródło
ConfigureAwait
pozwala uniknąć zakleszczenia w niektórych scenariuszach, nie uruchamiając się w oryginalnym kontekście wątku.