understanding - ¿Cuál es el propósito de "return await" en C#?
task.run async c# (6)
¿Hay algún escenario donde escribir un método como este?
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
en lugar de esto:
public Task<SomeResult> 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<T>
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?
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<SomeType>
, 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<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
Una persona que no Task<SomeResult>
familiarizada con la sintaxis (yo, por ejemplo) podría pensar que este método debería devolver una Task<SomeResult>
, 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 compilaría. La forma correcta es devolver el resultado de la tarea, por lo que la return await
.
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 bloqueo, no hay evidencia de que ese método esté involucrado en la "pila 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.
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<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return foo.DoAnotherThingAsync();
}
}
async Task<SomeResult> 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.
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<IFoo> GetIFooAsync()
{
return await GetFooAsync();
}
async Task<Foo> 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<Foo>
no se puede Task<IFoo>
directamente a Task<IFoo>
. 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<IFoo>
a empaquetar el resultado dentro de la Task<IFoo>
y listo.
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 MSDN de Stephen Toub sobre el tema.
Actualización: He escrito una publicación de blog que entra en mucho más detalle.