¿Cuál es el propósito de “return await” en C #?

¿Hay algún escenario donde escribir un método como este?

public async Task DoSomethingAsync() { // Some synchronous code might or might not be here... // return await DoAnotherThingAsync(); } 

en lugar de esto:

 public Task DoSomethingAsync() { // Some synchronous code might or might not be here... // return DoAnotherThingAsync(); } 

tendría un sentido?

¿Por qué usar return await construcción cuando puede devolver directamente la Task de la DoAnotherThingAsync() interna de DoAnotherThingAsync() ?

Veo el código con return await en tantos lugares, creo que debería haberme perdido algo. Pero por lo que entiendo, no usar palabras clave async / await en este caso y devolver directamente la tarea sería funcionalmente equivalente. ¿Por qué agregar una sobrecarga adicional de capa de await adicional?

Hay un caso furtivo cuando el return en el método normal y el return await en el método async se comportan de manera diferente: cuando se combina con el using (o, de manera más general, cualquier return await en un bloque de try ).

Considere estas dos versiones de un método:

 Task DoSomethingAsync() { using (var foo = new Foo()) { return foo.DoAnotherThingAsync(); } } async Task DoSomethingAsync() { using (var foo = new Foo()) { return await foo.DoAnotherThingAsync(); } } 

El primer método Dispose() el objeto Foo tan pronto como DoAnotherThingAsync() método DoAnotherThingAsync() , que probablemente sea mucho antes de que se complete. Esto significa que la primera versión probablemente tenga errores (porque Foo se elimina demasiado pronto), mientras que la segunda versión funcionará bien.

Si no necesita la async (es decir, puede devolver la Task directamente), entonces no use async .

Hay algunas situaciones en las que el return await es útil, como si tuviera dos operaciones asincrónicas que hacer:

 var intermediate = await FirstAsync(); return await SecondAwait(intermediate); 

Para obtener más información sobre el rendimiento de async , consulte el artículo y el video de MSDN de Stephen Toub sobre el tema.

Actualización: He escrito una publicación de blog que entra en mucho más detalle.

La única razón por la que querría hacerlo es si hay alguna otra await en el código anterior, o si de alguna manera está manipulando el resultado antes de devolverlo. Otra forma en que eso podría estar sucediendo es a través de un try/catch que cambia la manera en que se manejan las excepciones. Si no está haciendo nada de eso, entonces tiene razón, no hay ninguna razón para agregar la sobrecarga de hacer que el método sea async .

Otro caso que puede necesitar para esperar el resultado es este:

 async Task GetIFooAsync() { return await GetFooAsync(); } async Task GetFooAsync() { var foo = await CreateFooAsync(); await foo.InitializeAsync(); return foo; } 

En este caso, GetIFooAsync() debe esperar el resultado de GetFooAsync porque el tipo de T es diferente entre los dos métodos y Task no se puede Task directamente a Task . Pero si espera el resultado, simplemente se convierte en Foo que se puede IFoo directamente a IFoo . Luego, el método asíncrono simplemente Task a empaquetar el resultado dentro de la Task y listo.

Hacer el método asincrónico “thunk”, que de otro modo sería simple, crea una máquina de estado asíncrona en la memoria mientras que la no asincrónica no. Si bien eso puede indicarles a las personas que utilicen la versión no asincrónica porque es más eficiente (lo que es cierto), también significa que, en caso de locking, no hay evidencia de que ese método esté involucrado en la “stack de retorno / continuación”. lo que a veces hace que sea más difícil entender el hang.

Así que sí, cuando el rendimiento no es crítico (y generalmente no lo es) voy a lanzar una sincronización en todos estos métodos de procesador para que tenga la máquina de estado asíncrona que me ayude a diagnosticar cuelgues más tarde, y también para ayudar a garantizar que esos Los métodos thunk siempre evolucionan con el tiempo, y se asegurarán de devolver las tareas con errores en lugar de throw.

Esto también me confunde y siento que las respuestas anteriores pasaron por alto su pregunta real:

¿Por qué utilizar return aguarice construir cuando puede devolver Tarea directamente desde la invocación DoAnotherThingAsync () interna?

Bueno, a veces quieres una Task , pero la mayoría de las veces realmente quieres una instancia de SomeType , es decir, el resultado de la tarea.

De tu código:

 async Task DoSomethingAsync() { using (var foo = new Foo()) { return await foo.DoAnotherThingAsync(); } } 

Una persona que no Task familiarizada con la syntax (yo, por ejemplo) podría pensar que este método debería devolver una Task , pero dado que está marcado con async , significa que su tipo de devolución real es SomeResult . Si solo usa return foo.DoAnotherThingAsync() , estaría devolviendo una Tarea, que no se comstackría. La forma correcta es devolver el resultado de la tarea, por lo que la return await .