"wait" funziona, ma la chiamata all'attività. Il risultato si blocca / si blocca


126

Ho i seguenti quattro test e l'ultimo si blocca quando lo eseguo. Perché succede:

[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;
}

Uso questo metodo di estensione per 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; }
}

Perché l'ultimo test si blocca?


7
Dovresti evitare di restituire void da metodi asincroni. È solo per compatibilità con le versioni precedenti dei gestori di eventi esistenti, principalmente nel codice dell'interfaccia. Se il tuo metodo asincrono non restituisce nulla, dovrebbe restituire Task. Ho avuto numerosi problemi con MSTest e void restituendo test asincroni.
ghord

2
@ghord: MSTest non supporta affatto async voidi metodi di unit test; semplicemente non funzioneranno. Tuttavia, NUnit lo fa. Detto questo, concordo con il principio generale di preferire async Taskover async void.
Stephen Cleary

@StephenCleary Sì, anche se era consentito nelle beta di VS2012, il che causava tutti i tipi di problemi.
ghord

Risposte:


88

Ti stai imbattendo nella situazione di deadlock standard che descrivo sul mio blog e in un articolo di MSDN : il asyncmetodo sta tentando di pianificare la sua continuazione su un thread bloccato dalla chiamata a Result.

In questo caso, il tuo SynchronizationContextè quello utilizzato da NUnit per eseguire async voidi metodi di test. async TaskProverei invece a utilizzare metodi di test.


4
il passaggio all'attività asincrona ha funzionato, ora ho bisogno di leggere il contenuto dei tuoi collegamenti un paio di volte, signore.
Johan Larsson

@MarioLopez: La soluzione è usare " asyncfino in fondo" (come indicato nel mio articolo MSDN). In altre parole, come afferma il titolo del mio post sul blog, "non bloccare il codice asincrono".
Stephen Cleary

1
@StephenCleary cosa succede se devo chiamare un metodo asincrono all'interno di un costruttore? I costruttori non possono essere asincroni.
Raikol Amaro

1
@StephenCleary In quasi tutte le tue risposte su SO e nei tuoi articoli, tutto quello di cui ti ho visto parlare è sostituire Wait()con il metodo di chiamata async. Ma a me questo sembra spingere il problema a monte. Ad un certo punto, qualcosa deve essere gestito in modo sincrono. Cosa succede se la mia funzione è intenzionalmente sincrona perché gestisce thread di lavoro a esecuzione prolungata con Task.Run()? Come posso aspettare che finisca senza deadlock nel mio test NUnit?
void.pointer

1
@ void.pointer: At some point, something has to be managed synchronously.- per niente. Per le app dell'interfaccia utente, il punto di ingresso può essere un async voidgestore di eventi. Per le app server, il punto di ingresso può essere async Task<T>un'azione. È preferibile utilizzare asyncper entrambi per evitare di bloccare i thread. Puoi fare in modo che il tuo test NUnit sia sincrono o asincrono; se asincrono, fallo async Taskinvece di async void. Se è sincrono, non dovrebbe avere SynchronizationContextquindi non dovrebbe esserci un deadlock.
Stephen Cleary

223

Acquisizione di un valore tramite un metodo asincrono:

var result = Task.Run(() => asyncGetValue()).Result;

Chiamata sincrona di un metodo asincrono

Task.Run( () => asyncMethod()).Wait();

Non si verificheranno problemi di deadlock a causa dell'utilizzo di Task.Run.


15
-1 per incoraggiare l'uso di async voidmetodi di unit test e rimuovere le garanzie dello stesso thread fornite da SynchronizationContextdal sistema in prova.
Stephen Cleary

68
@StephenCleary: non c'è "enouraging" del vuoto asincrono. Sta semplicemente impiegando un costrutto c # valido per risolvere il problema del deadlock. Lo snippet di cui sopra è una soluzione semplice e indispensabile al problema dell'OP. Stackoverflow riguarda le soluzioni ai problemi, non l'auto-promozione prolissa.
Herman Schoenfeld

81
@StephenCleary: i tuoi articoli non articolano realmente la soluzione (almeno non chiaramente) e anche se avessi una soluzione, useresti tali costrutti indirettamente. La mia soluzione non usa esplicitamente i contesti, e allora? Il punto è che il mio funziona ed è una battuta. Non sono stati necessari due post sul blog e migliaia di parole per risolvere il problema. NOTA: Non uso nemmeno il vuoto asincrono , quindi non so davvero di cosa stai parlando .. Vedi "vuoto asincrono" ovunque nella mia risposta concisa e corretta?
Herman Schoenfeld

15
@HermanSchoenfeld, se aggiungessi il perché al come , credo che la tua risposta sarebbe molto utile.
ironstone13

19
So che è un po 'tardi, ma dovresti usare .GetAwaiter().GetResult()invece di in .Resultmodo che Exceptionnon sia impacchettato.
Camilo Terevinto

15

Puoi evitare di aggiungere deadlock ConfigureAwait(false)a questa riga:

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

Ho descritto questa trappola nel mio post sul blog Trappole di async / await


9

Stai bloccando l'interfaccia utente utilizzando la proprietà Task.Result. Nella documentazione MSDN hanno chiaramente menzionato che,

"La proprietà Result è una proprietà di blocco. Se provi ad accedervi prima che la sua attività sia terminata, il thread attualmente attivo è bloccato fino al completamento dell'attività e il valore è disponibile. Nella maggior parte dei casi, dovresti accedere al valore utilizzando Await o attendere invece di accedere direttamente alla proprietà. "

La soluzione migliore per questo scenario sarebbe rimuovere sia await che async dai metodi e utilizzare solo Task in cui stai restituendo il risultato. Non rovinerà la tua sequenza di esecuzione.


3

Se non ricevi alcun callback o il controllo riattacca, dopo aver chiamato la funzione asincrona servizio / API, devi configurare Context per restituire un risultato sullo stesso contesto chiamato.

Uso TestAsync().ConfigureAwait(continueOnCapturedContext: false);

Dovrai affrontare questo problema solo nelle applicazioni web, ma non in static void main.


ConfigureAwaitevita deadlock in determinati scenari non eseguendo nel contesto del thread originale.
davidcarr
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.