Esperar una tarea completada igual que la tarea. ¿Resultado?
Actualmente estoy leyendo " Libro de recetas de simultaneidad en C# " de Stephen Cleary y noté la siguiente técnica:
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
if (completedTask == timeoutTask)
return null;
return await downloadTask;
downloadTask
es una llamada a httpclient.GetStringAsync
y timeoutTask
se está ejecutando Task.Delay
.
En caso de que no haya expirado el tiempo de espera, ya downloadTask
se habrá completado. ¿Por qué es necesario hacer una segunda espera en lugar de regresar downloadTask.Result
, dado que la tarea ya está completada?
Ya hay algunas buenas respuestas/comentarios aquí, pero solo para intervenir...
Hay dos razones por las que prefiero await
( Result
o Wait
). La primera es que el manejo de errores es diferente; await
no envuelve la excepción en un archivo AggregateException
. Idealmente, el código asincrónico nunca debería tener que lidiar con AggregateException
nada, a menos que así lo desee específicamente .
La segunda razón es un poco más sutil. Como lo describo en mi blog (y en el libro), Result
/ Wait
puede causar interbloqueos y puede causar interbloqueos aún más sutiles cuando se usa en un async
método . Entonces, cuando leo el código y veo un Result
o Wait
, es una señal de advertencia inmediata. El Result
/ Wait
solo es correcto si está absolutamente seguro de que la tarea ya se completó. Esto no sólo 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
/ nuncaWait
deba usarse. Sigo estas pautas en mi propio código:
- El código asincrónico en una aplicación solo puede usar
await
. - El código de utilidad asincrónico (en una biblioteca) ocasionalmente puede usar
Result
/Wait
si el código realmente lo requiere. Este uso probablemente debería tener comentarios. - El código de tarea paralelo puede usar
Result
yWait
.
Tenga en cuenta que (1) es, con diferencia, el caso común, de ahí mi tendencia a utilizarlo await
en todas partes y tratar los demás casos como excepciones a la regla general.
Esto tiene sentido si timeoutTask
es un producto de Task.Delay
, lo cual creo que está en el libro.
Task.WhenAny
devuelve Task<Task>
, donde la tarea interna es una de las que pasaste como argumentos. Podría reescribirse así:
Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)
return null;
return downloadTask.Result;
En cualquier caso, como downloadTask
ya se completó, hay una diferencia mínima entre return await downloadTask
y return downloadTask.Result
. Es que este último arrojará AggregateException
lo que 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, dondequiera que maneje excepciones, debe verificar AggregateException
sus excepciones internas de todos modos para llegar a la causa del error.