Esperar en una tarea completada como la tarea. ¿Resultado?

Actualmente estoy leyendo “Concurrency in C # Cookbook” por 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 está ejecutando Task.Delay.

En caso de que no haya expirado, entonces downloadTask ya está completo. ¿Por qué es necesario esperar un segundo en lugar de devolver downloadTask.Result, dado que la tarea ya está completa?

Ya hay algunas buenas respuestas / comentarios aquí, pero solo para sonar en …

Hay dos razones por las que prefiero await sobre el Result (o Wait ). El primero es que el manejo de errores es diferente; await no envuelve la excepción en una AggregateException . Idealmente, el código asíncrono nunca debería tener que tratar con AggregateException en absoluto, a menos que específicamente lo desee .

La segunda razón es un poco más sutil. Como lo describo en mi blog (y en el libro), Result / Wait puede causar interlockings y puede causar interlockings aún más sutiles cuando se utiliza en un método async . Entonces, cuando estoy leyendo el código y veo un Result o Wait , eso es un indicador de advertencia inmediato. El Result / Wait solo es correcto si está absolutamente seguro de que la tarea ya está completa. No solo es difícil de ver de un vistazo (en el código del mundo real), sino que también es más quebradizo para los cambios de código.

Eso no quiere decir que el Result / Wait nunca debería ser usado. Sigo estas pautas en mi propio código:

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

Tenga en cuenta que (1) es, con mucho, el caso común, por lo tanto, mi tendencia a usar await todas partes y tratar los otros casos como excepciones a la regla general.

Esto tiene sentido si timeoutTask es un producto de Task.Delay , que creo que es lo que está en el libro.

Task.WhenAny devuelve Task , donde la tarea interna es una de las que pasó como argumentos. Podría ser reescrito así:

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

En cualquier caso, debido a que downloadTask ya se ha completado, hay una diferencia muy pequeña entre return await downloadTask y return downloadTask.Result . Es en este último que arrojará AggregateException que envuelve cualquier excepción original, como lo señala @KirillShlenskiy en los comentarios. El primero solo volvería a tirar la excepción original.

En cualquier caso, siempre que maneje excepciones, debe buscar AggregateException y sus excepciones internas de todos modos, para llegar a la causa del error.