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.