¿Hay algún escenario en el que 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 sentido?
¿Por qué usar return await
construct cuando puedes regresar directamente Task<T>
de la DoAnotherThingAsync()
invocación interna ?
Veo código return await
en tantos lugares, creo que podría haber perdido algo. Pero por lo que entiendo, no usar palabras clave asíncronas / esperar en este caso y devolver directamente la Tarea sería funcionalmente equivalente. ¿Por qué agregar sobrecarga adicional de await
capa adicional ?
c#
.net
.net-4.5
async-await
TX_
fuente
fuente
Respuestas:
Hay un caso disimulado cuando
return
en el método normal yreturn await
enasync
comportan método diferente: cuando se combina conusing
(o, más en general, cualquierreturn await
en untry
bloque).Considere estas dos versiones de un método:
El primer método será
Dispose()
elFoo
objeto tan pronto como elDoAnotherThingAsync()
método regrese, lo que probablemente sea mucho antes de que realmente se complete. Esto significa que la primera versión probablemente tenga errores (porqueFoo
se desecha demasiado pronto), mientras que la segunda versión funcionará bien.fuente
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
regresavoid
. Necesitarías algo como esoreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. Pero no sé por qué harías eso cuando puedes usar la segunda opción.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. El caso de uso es bastante simple: si está en .NET 4.0 (como la mayoría), aún puede escribir código asíncrono de esta manera, que funcionará muy bien desde 4.5 aplicaciones.Foo
solo después de que seTask
complete la devolución , lo que no me gusta, porque innecesariamente introduce concurrencia.Si no lo necesita
async
(es decir, puede devolverloTask
directamente), no lo useasync
.Hay algunas situaciones en las que
return await
es útil, como si tiene dos operaciones asincrónicas que hacer:Para obtener más información sobre el
async
rendimiento, 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.
fuente
await
es útil en el segundo caso? ¿Por qué no hacerreturn SecondAwait(intermediate);
?return SecondAwait(intermediate);
alcanzaría el objetivo también en ese caso? Creo quereturn await
es redundante aquí también ...await
en la primera línea, también debe usarla en la segunda.async
versión usará menos recursos (hilos) que su versión síncrona.SecondAwait
es `cadena, el mensaje de error es:" CS4016: Dado que este es un método asíncrono, la expresión de retorno debe ser de tipo 'cadena' en lugar de 'Tarea <cadena>' ".La única razón por la que querría hacerlo es si hay algún otro
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 untry/catch
cambio en la forma en que se manejan las excepciones. Si no está haciendo nada de eso, tiene razón, no hay razón para agregar la sobrecarga de hacer el métodoasync
.fuente
return await
necesario (en lugar de simplemente devolver la tarea de invocación de niños) incluso si hay otro espera en el código anterior . ¿Podría por favor dar una explicación?async
, ¿cómo esperaría la primera tarea? Debe marcar el método comoasync
si quisiera usar cualquier espera. Si el método está marcado comoasync
y tiene unawait
código anterior, entonces necesitaawait
la segunda operación asíncrona para que sea del tipo apropiado. Si acaba de eliminar,await
entonces no se compilaría ya que el valor de retorno no sería del tipo adecuado. Como el método esasync
el resultado, siempre se envuelve en una tarea.async
método no devuelve una tarea, devuelve el resultado de la tarea que luego se ajustará.Task<Type>
explícitamente, mientras queasync
dictemos regresarType
(en lo que el compilador se convertiríaTask<Type>
).async
es solo azúcar sintáctico para conectar explícitamente las continuaciones. No necesitaasync
hacer nada, pero cuando realiza casi cualquier operación asincrónica no trivial es mucho más fácil trabajar con ella. Por ejemplo, el código que proporcionó en realidad no propaga los errores como desea, y hacerlo correctamente en situaciones aún más complejas comienza a ser mucho más difícil. Si bien nunca lo necesitaasync
, las situaciones que describo son donde está agregando valor para usarlo.Otro caso que puede necesitar para esperar el resultado es este:
En este caso,
GetIFooAsync()
debe esperar el resultado deGetFooAsync
porque el tipo deT
es diferente entre los dos métodos yTask<Foo>
no se puede asignar directamenteTask<IFoo>
. Pero si espera el resultado, se convierte en loFoo
que es directamente asignableIFoo
. Luego, el método asíncrono simplemente vuelve a empaquetar el resultado dentroTask<IFoo>
y fuera de donde vaya.fuente
Task<>
es invariable.Hacer asincrónico el método "thunk" de otra manera simple crea una máquina de estado asincrónica en la memoria, mientras que la no asincrónica no. Si bien eso a menudo puede apuntar a la gente a usar la versión no asíncrona porque es más eficiente (lo cual es cierto), también significa que en el caso de un bloqueo, no tienes 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 bloqueo.
Entonces, sí, cuando el rendimiento no es crítico (y generalmente no lo es), lanzaré asíncrono en todos estos métodos de thunk para que tenga la máquina de estado asíncrona que me ayude a diagnosticar bloqueos más tarde, y también para ayudar a garantizar que si esos Los métodos thunk siempre evolucionan con el tiempo, se asegurarán de devolver las tareas fallidas en lugar de tirar.
fuente
Si no va a utilizar return aguarda, puede arruinar el seguimiento de la pila durante la depuración o cuando se imprime en los registros de excepciones.
Cuando devuelve la tarea, el método cumplió su propósito y está fuera de la pila de llamadas. Cuando lo usa,
return await
lo está dejando en la pila de llamadas.Por ejemplo:
Pila de llamadas cuando se usa esperar: A esperando la tarea de B => B esperando la tarea de C
Pila de llamadas cuando no se usa esperar: A esperando la tarea de C, que B ha devuelto.
fuente
Esto también me confunde y siento que las respuestas anteriores pasaron por alto su pregunta real:
Bueno, a veces realmente quieres un
Task<SomeType>
, pero la mayoría de las veces realmente quieres una instanciaSomeType
, es decir, el resultado de la tarea.De tu código:
Una persona que no
Task<SomeResult>
esté familiarizada con la sintaxis (yo, por ejemplo) podría pensar que este método debería devolver a , pero como está marcado conasync
, significa que su tipo de retorno real esSomeResult
. Si solo usareturn 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 elreturn await
.fuente
var task = DoSomethingAsync();
le daría una tarea, noT
async/await
cosa. A mi entenderTask task = DoSomethingAsync()
, mientrasSomething something = await DoSomethingAsync()
ambos trabajan. El primero le da la tarea adecuada, mientras que el segundo, debido a laawait
palabra clave, le da el resultado de la tarea una vez que se completa. Podría, por ejemplo, tenerTask task = DoSomethingAsync(); Something something = await task;
.