Attendere un'attività completata come un'attività. Risultato?


117

Attualmente sto leggendo " Concurrency in C # Cookbook " di Stephen Cleary e ho notato la seguente tecnica:

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  

downloadTaskè una chiamata a httpclient.GetStringAsynced timeoutTaskè in esecuzione Task.Delay.

Nel caso in cui non sia scaduto, allora downloadTaskè già completato. Perché è necessario fare una seconda attesa invece di tornare downloadTask.Result, visto che l'attività è già completata?


3
C'è un po 'di contesto che manca qui e, a meno che le persone non abbiano prontamente accesso al libro, dovrai includerlo. Cos'è downloadTaske timeoutTask? Cosa fanno?
Mike Perrenoud

7
Non vedo un controllo effettivo per il completamento con successo qui. Il compito potrebbe benissimo essere criticato, e in tal caso il comportamento sarà diverso ( AggregateExceptioncon la Resultvs prima eccezione tramite ExceptionDispatchInfocon await). Discusso in modo più dettagliato in "Task Exception Handling in .NET 4.5" di Stephen Toub: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Kirill Shlenskiy

dovresti renderlo una risposta @KirillShlenskiy
Carsten

@MichaelPerrenoud Hai ragione, grazie per averlo notato, modifico la domanda.
julio.g

Risposte:


160

Ci sono già alcune buone risposte / commenti qui, ma solo per intervenire ...

Ci sono due motivi per cui preferisco awaitover Result(or Wait). Il primo è che la gestione degli errori è diversa; awaitnon racchiude l'eccezione in un file AggregateException. Idealmente, il codice asincrono non dovrebbe mai avere a che fare con AggregateExceptionnulla, a meno che non lo desideri specificamente .

La seconda ragione è un po 'più sottile. Come ho descritto sul mio blog (e nel libro), Result/ Waitpuò causare deadlock e può causare deadlock ancora più sottili se usato in un asyncmetodo . Quindi, quando leggo il codice e vedo un Resulto Wait, questo è un flag di avviso immediato. Il Result/ Waitè corretta solo se si è assolutamente sicuri che l'operazione è già stata completata. Non solo questo è difficile da vedere a colpo d'occhio (nel codice del mondo reale), ma è anche più fragile alle modifiche del codice.

Questo non vuol dire che Result/ nonWait dovrebbe mai essere usato. Seguo queste linee guida nel mio codice:

  1. Il codice asincrono in un'applicazione può essere utilizzato solo await.
  2. Il codice di utilità asincrono (in una libreria) può occasionalmente utilizzare Result/ Waitse il codice lo richiede davvero. Tale utilizzo dovrebbe probabilmente contenere commenti.
  3. Il codice attività parallela può utilizzare Resulte Wait.

Si noti che (1) è di gran lunga il caso comune, da qui la mia tendenza a usare awaitovunque e trattare gli altri casi come eccezioni alla regola generale.


Abbiamo riscontrato il deadlock utilizzando "result" invece di "await" nei nostri progetti. la parte incasinata non ha errori di compilazione e il tuo codice diventa instabile dopo un po '.
Ahmad Mousavi

@Stephen potresti per favore spiegarmi perché "Idealmente, il codice asincrono non dovrebbe mai avere a che fare con AggregateException, a meno che non lo voglia specificamente"
vcRobe

4
@vcRobe Perché awaitimpedisce il AggregateExceptionwrapper. AggregateExceptionè stato progettato per la programmazione parallela, non per la programmazione asincrona.
Stephen Cleary

2
> "L'attesa è corretta solo se sei assolutamente sicuro che l'attività sia già completata." .... Allora perché si chiama Aspetta?
Ryan The Leach

4
@ RyanTheLeach: lo scopo originale Waitera quello di unirsi alle istanze di parallelismo dinamico delle attività Task . Usarlo per attendere Taskistanze asincrone è pericoloso. Microsoft ha considerato l'introduzione di un nuovo tipo "Promise", ma ha scelto di utilizzare Taskinvece quello esistente ; il compromesso del riutilizzo del Tasktipo esistente per attività asincrone è che si finisce con diverse API che semplicemente non dovrebbero essere utilizzate nel codice asincrono.
Stephen Cleary

12

Questo ha senso se timeoutTaskè un prodotto di Task.Delaycui credo sia contenuto nel libro.

Task.WhenAnyrestituisce Task<Task>, dove l'attività interna è una di quelle che hai passato come argomenti. Potrebbe essere riscritto in questo modo:

Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)  
  return null;  
return downloadTask.Result; 

In entrambi i casi, poiché downloadTaskè già stato completato, c'è una differenza minima tra return await downloadTaske return downloadTask.Result. È in quanto quest'ultimo lancerà AggregateExceptionche avvolge qualsiasi eccezione originale, come sottolineato da @KirillShlenskiy nei commenti. Il primo rigetterebbe semplicemente l'eccezione originale.

In entrambi i casi, ovunque tu gestisca le eccezioni, dovresti comunque controllare AggregateExceptione le sue eccezioni interne, per arrivare alla causa dell'errore.

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.