c# - understanding - ¿Por qué usar Async/esperar todo el camino?
task.run c# (5)
Me gustaría obtener una aclaración sobre cuál es el beneficio adicional del uso de Await y Async hasta el final.
Si mi aplicación está llamando, espere Func1()
(así que no hay bloqueo a la interfaz de usuario aquí). y Func1
está llamando a esperar a Func2()
, pero los resultados de Func2()
son importantes para que Func1
complete su trabajo, entonces, ¿por qué tendría que hacer que Func2()
viable? Func1()
ejecución de Func1()
tomará el mismo tiempo porque está esperando a que Func2
termine. Todo lo que está haciendo la espera aquí es agregar la sobrecarga de StateMachine.
¿Me estoy perdiendo de algo?
"Este método asíncrono carece de operadores ''a la espera'' y se ejecutará de forma síncrona" es lo que sucede si no espera un método asíncrono. Para aprovechar los beneficios de un método asíncrono, debe esperar, convirtiendo a la persona que llama en un método asíncrono que se puede esperar, etc.
En el diagrama de flujo, puede ver que el método asíncrono devuelve una Tarea (una promesa de trabajo que se completará en el futuro) y le otorga el control a quien llama. El interlocutor puede continuar con el trabajo mientras tanto no dependa de este resultado. Claramente, esto necesita aumentar la pila de llamadas para encontrar todo este trabajo provechoso que se puede hacer sin el resultado (en una aplicación de interfaz de usuario, este trabajo incluiría desbloquear la interfaz de usuario, por lo que es asíncrono hasta el controlador de eventos). en el siguiente ejemplo).
De mi mala lectura inicial. Así que ha encontrado un código asíncrono al que necesita llamar, ¿vale la pena el patrón asíncrono de espera que distribuye su código?
He escuchado mucho que el problema principal con async-await es que es una sintaxis demasiado fácil para lo que realmente hace. El flujo del programa se complica. Realmente me gusta async-await, pero desafortunadamente, en la mayoría de los códigos asíncronos que he visto no vale la pena y solo está arruinando innecesariamente mi pila de llamadas.
Una buena cosa a tener en cuenta es la regla de los 50ms .
"Esta es la regla que Microsoft siguió con las API de WinRT; cualquier cosa que tome menos de 50 ms se considera" rápida "y lo suficientemente cercana a" inmediata "para que no requieran una API asíncrona".
Esto se está utilizando en el contexto de alentar async-await. Sin embargo, creo que también debería aplicarse para decirle a los desarrolladores que utilizan async-aguarda que la funcionalidad esencialmente inmediata lo elimine.
El principal beneficio es el hecho de que la espera de un método asíncrono devuelve el subproceso de trabajo al grupo que se usará en otras llamadas (solicitudes web a su aplicación web .NET MVC, por ejemplo). El trabajo asíncrono se realiza en un subproceso de finalización de E / S. Cuando el método esperado finalice, otro subproceso de trabajo recopilará los resultados y reanudará la ejecución. Esto evita el agotamiento del grupo de subprocesos de trabajo y permite que su aplicación maneje más carga (dependiendo de la CPU, la memoria y el rendimiento de la red).
En cuanto a "esperar hasta el final", eso me parece un problema. Por lo general, await
está asociado a un recurso externo (llamada DB, solicitud HTTP, etc.) en el que su aplicación debe esperar. Si espera un código que no tiene dependencias de E / S externas, está creando una sobrecarga que no es necesaria. Es posible tener varias awaits
en una cadena de métodos async
, pero esperar un código que en sí mismo le await
pero que no tiene otra dependencia externa de E / S no es bueno y solo agregará una sobrecarga de devolución de llamada / compilador.
En una aplicación basada en UI, async await proporciona una manera muy breve de manejar llamadas asíncronas que resultan en actualizaciones de UI. Si el manejador de ''nivel superior'' de la interfaz de usuario está esperando un resultado, entonces potencialmente no hay un beneficio real en toda la cadena haciendo lo mismo a menos que tenga sentido hacerlo. Un objetivo de diseño de async await era hacer que la programación async pareciera más sincrónica y continua y no tener devoluciones de llamadas dispersas, etc., lo que hace que la codificación asíncrona sea más evaluable. No es algo que necesites usar en cualquier lugar de manera arbitraria.
Generalmente, el razonamiento detrás de async
/ await
es al revés:
Por la razón que sea, decides que Func2
sería más fácil de escribir usando await
. Por lo tanto, simplifica el método agregando las await
deseadas, lo que significa que también tiene que cambiar la firma del método (ahora incluirá la palabra clave async
y un tipo de retorno de Task
o Task<T>
).
Debido a que el tipo de retorno ha cambiado, ya no puede llamar a Func2
como solía hacerlo ( var result = Func2();
), por lo que ahora debe cambiar el método de llamada, Func1
. La forma más fácil de adaptar Func1
a menudo será hacerlo async
y await Func2()
.
Luego, por el mismo motivo (firma modificada), deberá cambiar todas las llamadas a Func1
, y así sucesivamente hasta que llegue a algún tipo de punto de entrada (ya sea un controlador de eventos de UI, su método Main
o algún otro).
Por lo tanto, no comienza a hacer que el método "más externo" async
y siga a los métodos "internos" (llamados); Por lo general, las cosas se vuelven async
mientras se va en la dirección opuesta (desde los métodos "más internos" hasta los que llaman). Esta otra respuesta llama a esta idea "asíncrona hasta el final" .
Un mejor eslogan es async
hasta el final . Porque comienza con una operación asíncrona y hace que su interlocutor sea asíncrono y luego el siguiente interlocutor, etc.
Debería usar async-await
cuando tenga una operación inherentemente asíncrona (generalmente I / O pero no necesariamente) y no quiere perder un hilo esperando que la operación se complete. Elegir una operación async
en lugar de una síncrona no acelera la operación. Tomará la misma cantidad de tiempo (o incluso más). Simplemente habilita ese hilo para continuar ejecutando algún otro trabajo vinculado a la CPU en lugar de perder recursos.
Pero para poder await
esa operación, el método debe ser async
y la persona que llama necesita await
y así sucesivamente.
Así que async
hasta el final le permite realizar una llamada asíncrona y liberar cualquier subproceso. Si no es async
completamente, entonces se bloquea algún hilo.
Así que esto:
async Task FooAsync()
{
await Func1();
// do other stuff
}
async Task Func1()
{
await Func2();
// do other stuff
}
async Task Func2()
{
await tcpClient.SendAsync();
// do other stuff
}
Es mejor que esto:
void Foo()
{
Func1();
// do other stuff
}
void Func1()
{
Func2().Wait(); // Synchronously blocking a thread.
// do other stuff
}
async Task Func2()
{
await tcpClient.SendAsync();
// do other stuff
}