await vs Task.Wait - Deadlock?


197

Non capisco bene la differenza tra Task.Waite await.

Ho qualcosa di simile alle seguenti funzioni in un servizio WebAPI ASP.NET:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Dove si Getbloccherà.

Cosa potrebbe causare questo? Perché questo non causa problemi quando utilizzo un'attesa di blocco anziché await Task.Delay?


@Servy: tornerò con un repository non appena avrò tempo. Per ora funziona con il Task.Delay(1).Wait()quale è abbastanza buono.
ronag,

2
Task.Delay(1).Wait()è praticamente la stessa cosa di Thread.Sleep(1000). Nel codice di produzione effettivo è raramente appropriato.
Servito il

@ronag: stai WaitAllcausando il deadlock. Vedi il link al mio blog nella mia risposta per maggiori dettagli. Dovresti usare await Task.WhenAllinvece.
Stephen Cleary,

6
@ronag Poiché hai ConfigureAwait(false)una sola chiamata per Baro Rosnon si bloccherà, ma perché hai un enumerabile che sta creando più di una e che sta aspettando su tutte quelle, la prima barra bloccherà la seconda. Se await Task.WhenAllinvece di attendere tutte le attività, in modo da non bloccare il contesto ASP, vedrai il metodo tornare normalmente.
Servito il

2
@ronag L'altra opzione sarebbe quella di aggiungere .ConfigureAwait(false) fino in fondo all'albero fino a quando non si blocca, in questo modo nulla sta mai cercando di tornare al contesto principale; avrebbe funzionato. Un'altra opzione sarebbe quella di creare un contesto di sincronizzazione interno. Link . Se lo metti Task.WhenAlldentro, AsyncPump.Runsi bloccherà efficacemente tutto senza che tu ne abbia bisogno da ConfigureAwaitnessuna parte, ma probabilmente è una soluzione troppo complessa.
Servito il

Risposte:


270

Waite await- sebbene concettualmente simili - in realtà sono completamente diversi.

Waitsi bloccherà in modo sincrono fino al completamento dell'attività. Quindi il thread corrente viene letteralmente bloccato in attesa del completamento dell'attività. Come regola generale, dovresti usare " asyncfino in fondo"; cioè, non bloccare il asynccodice. Sul mio blog, vado nei dettagli di come il blocco nel codice asincrono provoca deadlock .

awaitattenderà in modo asincrono fino al completamento dell'attività. Ciò significa che il metodo corrente è "in pausa" (il suo stato viene acquisito) e il metodo restituisce un'attività incompleta al chiamante. Successivamente, al awaittermine dell'espressione, il resto del metodo viene pianificato come continuazione.

Hai anche menzionato un "blocco cooperativo", con il quale presumo intendi che un'attività che stai Waiteseguendo potrebbe essere eseguita sul thread in attesa. Ci sono situazioni in cui ciò può accadere, ma è un'ottimizzazione. Esistono molte situazioni in cui non può accadere, ad esempio se l'attività è per un altro programmatore o se è già avviata o se si tratta di un'attività non di codice (come nell'esempio di codice: Waitimpossibile eseguire l' Delayattività in linea perché non c'è codice per questo).

La mia async/ awaitintro potrebbe essere utile.


5
Penso che ci sia un malinteso, Waitfunziona bene awaitdeadlock.
ronag,

1
Chiaramente: Sì, se sostituisco il mio await Task.Delay(1)con Task.Delay(1).Wait()il servizio funziona bene, altrimenti si blocca.
ronag,

5
No, l'utilità di pianificazione non lo farà. Waitblocca il thread e non può essere utilizzato per altre cose.
Stephen Cleary,

8
@ronag La mia ipotesi è che hai appena confuso i nomi dei tuoi metodi e il tuo deadlock è stato effettivamente causato dal codice di blocco e ha funzionato con il awaitcodice. O quello, o il deadlock non era correlato a nessuno dei due e hai diagnosticato erroneamente il problema.
Servi

3
@hexterminator: questo è di progettazione - funziona benissimo per le app UI, ma tende ad intralciare le app ASP.NET. ASP.NET Core ha risolto questo problema rimuovendo il SynchronizationContext, quindi il blocco all'interno di una richiesta ASP.NET Core non è più un deadlock.
Stephen Cleary,

5

Sulla base di ciò che ho letto da diverse fonti:

Un await non blocca il thread su cui è in esecuzione. Al contrario, fa sì che il compilatore registri il resto del asyncmetodo come continuazione dell'attività attesa. Il controllo quindi ritorna al chiamante del asyncmetodo. Al termine dell'attività, viene richiamata la sua continuazione e l'esecuzione del asyncmetodo riprende da dove era stata interrotta.

Aspettare un singolo task , puoi chiamarne il Task.Waitmetodo. Una chiamata al Waitmetodo blocca il thread chiamante fino al completamento dell'esecuzione dell'istanza della singola classe. Il Wait()metodo senza parametri viene utilizzato per attendere incondizionatamente fino al completamento di un'attività. L'attività simula il lavoro chiamando il Thread.Sleepmetodo in modalità sospensione per due secondi.

Questo articolo è una buona lettura.


3
"Non è tecnicamente scorretto allora? Qualcuno può chiarire, per favore?" - posso chiarire; lo stai facendo come una domanda? (Voglio solo essere chiaro se stai chiedendo vs risposta). Se stai chiedendo: potrebbe funzionare meglio come una domanda separata; è improbabile che qui si raccolgano nuove risposte come risposta
Marc Gravell

1
Ho risposto alla domanda e ha chiesto una domanda separata per il dubbio che ho avuto qui stackoverflow.com/questions/53654006/... Grazie @MarcGravell. Puoi per favore rimuovere il tuo voto di cancellazione per la risposta ora?
Ayushmati,

"Puoi per favore rimuovere il tuo voto di cancellazione per la risposta ora?" - quello non è mio; grazie a ♦, qualsiasi mio voto sarebbe entrato in vigore immediatamente. Tuttavia, non penso che questo risponda ai punti chiave della domanda, che riguarda il comportamento di deadlock.
Marc Gravell

Questo non è vero. Fino al primo momento di attesa non raggiunto tutto è bloccato
user1785960

-2

Alcuni fatti importanti non sono stati forniti in altre risposte:

"async await" è più complesso a livello CIL e quindi costa tempo di memoria e CPU.

Qualsiasi attività può essere annullata se il tempo di attesa è inaccettabile.

Nel caso in cui "Async attende" non abbiamo un gestore per tale attività per annullarlo o monitorarlo.

L'uso di Task è più flessibile di "async await".

Qualsiasi funzionalità di sincronizzazione può essere completata da asincrona.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

"async waitit" genera molti problemi. Al momento non è possibile attendere che l'istruzione venga raggiunta senza runtime e debug del contesto.Se prima attendi non raggiunto tutto è bloccato . Alcune volte anche l'attesa sembra essere raggiunta, ma tutto è bloccato:

https://github.com/dotnet/runtime/issues/36063

Non vedo il motivo per cui devo convivere con la duplicazione del codice per il metodo sync e asincrono o usando gli hack.

Conclusione: creare attività manualmente e controllarle è molto meglio. Handler to Task offre un maggiore controllo. Siamo in grado di monitorare le attività e gestirle:

https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem

Mi scusi per il mio inglese.

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.