¿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 awaitconstruct cuando puedes regresar directamente Task<T>de la DoAnotherThingAsync()invocación interna ?
Veo código return awaiten 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 awaitcapa adicional ?
c#
.net
.net-4.5
async-await
TX_
fuente
fuente

Respuestas:
Hay un caso disimulado cuando
returnen el método normal yreturn awaitenasynccomportan método diferente: cuando se combina conusing(o, más en general, cualquierreturn awaiten untrybloque).Considere estas dos versiones de un método:
El primer método será
Dispose()elFooobjeto 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 (porqueFoose 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.Foosolo después de que seTaskcomplete la devolución , lo que no me gusta, porque innecesariamente introduce concurrencia.Si no lo necesita
async(es decir, puede devolverloTaskdirectamente), no lo useasync.Hay algunas situaciones en las que
return awaites útil, como si tiene dos operaciones asincrónicas que hacer:Para obtener más información sobre el
asyncrendimiento, 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
awaites ú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 awaites redundante aquí también ...awaiten la primera línea, también debe usarla en la segunda.asyncversión usará menos recursos (hilos) que su versión síncrona.SecondAwaites `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
awaiten 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/catchcambio 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 awaitnecesario (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 comoasyncsi quisiera usar cualquier espera. Si el método está marcado comoasyncy tiene unawaitcódigo anterior, entonces necesitaawaitla segunda operación asíncrona para que sea del tipo apropiado. Si acaba de eliminar,awaitentonces no se compilaría ya que el valor de retorno no sería del tipo adecuado. Como el método esasyncel resultado, siempre se envuelve en una tarea.asyncmétodo no devuelve una tarea, devuelve el resultado de la tarea que luego se ajustará.Task<Type>explícitamente, mientras queasyncdictemos regresarType(en lo que el compilador se convertiríaTask<Type>).asynces solo azúcar sintáctico para conectar explícitamente las continuaciones. No necesitaasynchacer 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 deGetFooAsyncporque el tipo deTes diferente entre los dos métodos yTask<Foo>no se puede asignar directamenteTask<IFoo>. Pero si espera el resultado, se convierte en loFooque 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 awaitlo 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, noTasync/awaitcosa. A mi entenderTask task = DoSomethingAsync(), mientrasSomething something = await DoSomethingAsync()ambos trabajan. El primero le da la tarea adecuada, mientras que el segundo, debido a laawaitpalabra clave, le da el resultado de la tarea una vez que se completa. Podría, por ejemplo, tenerTask task = DoSomethingAsync(); Something something = await task;.