Espere una tarea completada igual que la tarea.

117

Actualmente estoy leyendo " Concurrencia en C # Cookbook " de Stephen Cleary, y noté la siguiente técnica:

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

downloadTaskes una llamada a httpclient.GetStringAsyncy se timeoutTaskestá ejecutando Task.Delay.

En el caso de que no se haya agotado el tiempo de espera, entonces downloadTaskya está completado. ¿Por qué es necesario esperar un segundo en lugar de regresar downloadTask.Result, dado que la tarea ya está completada?

julio.g
fuente
3
Aquí falta un poco de contexto y, a menos que las personas tengan acceso al libro, tendrá que incluirlo. ¿Qué es downloadTasky timeoutTask? ¿Qué hacen?
Mike Perrenoud
7
No veo una verificación real de finalización exitosa aquí. La tarea podría muy bien fallar, y en ese caso el comportamiento será diferente ( AggregateExceptioncon Resultvs primera excepción vía ExceptionDispatchInfocon await). Discutido con más detalle en "Task Exception Handling in .NET 4.5" de Stephen Toub: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Kirill Shlenskiy
deberías hacer de esto una respuesta @KirillShlenskiy
Carsten
@MichaelPerrenoud Tienes razón, gracias por darte cuenta, editaré la pregunta.
julio.g

Respuestas:

160

Ya hay algunas buenas respuestas / comentarios aquí, pero solo para intervenir ...

Hay dos razones por las que prefiero awaitsobre Result(o Wait). La primera es que el manejo de errores es diferente; awaitno envuelve la excepción en un AggregateException. Idealmente, el código asincrónico nunca debería tener que lidiar AggregateExceptionen absoluto, a menos que lo desee específicamente .

La segunda razón es un poco más sutil. Como describo en mi blog (y en el libro), Result/ Waitpuede causar interbloqueos y puede causar interbloqueos aún más sutiles cuando se usa en un asyncmétodo . Entonces, cuando estoy leyendo el código y veo un Resulto Wait, es una señal de advertencia inmediata. La Result/ Waitsolo es correcta si está absolutamente seguro de que la tarea ya se completó. Esto no solo es difícil de ver de un vistazo (en el código del mundo real), sino que también es más frágil para los cambios de código.

Eso no quiere decir que Result/ Waitdebe no ser utilizado. Sigo estas pautas en mi propio código:

  1. El código asincrónico en una aplicación solo se puede usar await.
  2. El código de utilidad asincrónico (en una biblioteca) ocasionalmente puede usar Result/ Waitsi el código realmente lo requiere. Tal uso probablemente debería tener comentarios.
  3. El código de tarea paralelo puede usar Resulty Wait.

Tenga en cuenta que (1) es, con mucho, el caso común, de ahí mi tendencia a usar en awaittodas partes y tratar los otros casos como excepciones a la regla general.

Stephen Cleary
fuente
Encontramos el punto muerto usando 'resultado' en lugar de 'esperar' en nuestros proyectos. la parte desordenada es que no tiene ningún error de compilación y su código se vuelve inestable después de un tiempo.
Ahmad Mousavi
@ Stephen, ¿podría explicarme por qué "Idealmente, el código asincrónico nunca debería tener que lidiar con AggregateException, a menos que lo desee específicamente"
vcRobe
4
@vcRobe Porque awaitevita la AggregateExceptionenvoltura. AggregateExceptionfue diseñado para programación paralela, no programación asincrónica.
Stephen Cleary
2
> "Esperar solo es correcto si está absolutamente seguro de que la tarea ya se completó". .... Entonces, ¿por qué se llama Espera?
Ryan The Leach
4
@RyanTheLeach: El propósito original de Waitera unirse a instancias de Dynamic Task Parallelism Task . Usarlo para esperar Taskinstancias asincrónicas es peligroso. Microsoft consideró introducir un nuevo tipo de "Promesa", pero optó por utilizar el existente en su Tasklugar; la compensación de reutilizar el Tasktipo existente para tareas asincrónicas es que terminas con varias API que simplemente no deberían usarse en código asincrónico.
Stephen Cleary
12

Esto tiene sentido si timeoutTaskes un producto de Task.Delaylo que creo que está en el libro.

Task.WhenAnydevuelve Task<Task>, donde la tarea interna es una de las que pasó como argumentos. Se podría reescribir así:

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

En cualquier caso, debido a que downloadTaskya se completó, hay una diferencia mínima entre return await downloadTasky return downloadTask.Result. Es en que este último arrojará lo AggregateExceptionque envuelve cualquier excepción original, como lo señaló @KirillShlenskiy en los comentarios. El primero simplemente volvería a lanzar la excepción original.

En cualquier caso, donde sea que maneje las excepciones, debe verificar AggregateExceptionsus excepciones internas de todos modos, para llegar a la causa del error.

ratio nasal
fuente