Estoy tratando de entender async esperar en la forma más simple. Quiero crear un método muy simple que agregue dos números por el bien de este ejemplo, concedido, no es tiempo de procesamiento, solo es cuestión de formular un ejemplo aquí.
Ejemplo 1
private async Task DoWork1Async()
{
int result = 1 + 2;
}
Ejemplo 2
private async Task DoWork2Async()
{
Task.Run( () =>
{
int result = 1 + 2;
});
}
Si espero, ¿ DoWork1Async()
se ejecutará el código de forma síncrona o asíncrona?
¿Debo envolver el código de sincronización Task.Run
para hacer que el método sea esperable Y asíncrono para no bloquear el hilo de la interfaz de usuario?
Estoy tratando de averiguar si mi método es Task
o devuelve, Task<T>
¿tengo que ajustar el código Task.Run
para hacerlo asíncrono?
Estúpida pregunta, estoy seguro, pero veo ejemplos en la red donde la gente espera un código que no tiene nada asíncrono y no está envuelto en un Task.Run
o StartNew
.
fuente
Respuestas:
Primero, aclaremos algo de terminología: "asíncrono" (
async
) significa que puede ceder el control al hilo de llamada antes de que comience. En unasync
método, esos puntos de "rendimiento" sonawait
expresiones.Esto es muy diferente al término "asíncrono", ya que (mal) usado por la documentación de MSDN durante años significa "se ejecuta en un hilo de fondo".
Para confundir aún más el tema,
async
es muy diferente de "espera"; Hay algunosasync
métodos cuyos tipos de retorno no son esperados, y muchos métodos que devuelven tipos que no lo sonasync
.Suficiente sobre lo que no son ; esto es lo que son :
async
palabra clave permite un método asincrónico (es decir, permiteawait
expresiones).async
los métodos pueden regresarTask
,Task<T>
o (si debe hacerlo)void
.Task
yTask<T>
.Entonces, si reformulamos su pregunta a "¿cómo puedo ejecutar una operación en un subproceso en segundo plano de una manera que sea esperable", la respuesta es usar
Task.Run
:(Pero este patrón es un enfoque pobre; ver más abajo).
Pero si su pregunta es "¿cómo creo un
async
método que pueda ceder el paso a su interlocutor en lugar de bloquearlo", la respuesta es declarar el métodoasync
y usarloawait
para sus puntos de "ceder":Por lo tanto, el patrón básico de las cosas es que el
async
código dependa de "esperables" en susawait
expresiones. Estos "esperables" pueden ser otrosasync
métodos o simplemente métodos regulares que devuelven objetos esperables. Métodos habituales que regresanTask
/Task<T>
pueden utilizarTask.Run
para ejecutar código en un subproceso en segundo plano, o (más comúnmente) que pueden utilizarTaskCompletionSource<T>
o uno de sus accesos directos (TaskFactory.FromAsync
,Task.FromResult
, etc.). Yo no recomiendo el embalaje de un método completo enTask.Run
; los métodos síncronos deben tener firmas síncronas, y debe dejarse en manos del consumidor si debe estar envuelto en unTask.Run
:Tengo una
async
/await
introducción en mi blog; al final hay algunos buenos recursos de seguimiento. Los documentos de MSDN para tambiénasync
son inusualmente buenos.fuente
async
métodos deben devolverTask
,Task<T>
ovoid
.Task
yTask<T>
están a la espera;void
no es.async void
se compilará una firma de método, es una idea bastante terrible, ya que pierde el puntero a su tarea asíncronavoid
no está a la espera.Task.Run
(comoDoWorkAsync
en esta respuesta). UsarTask.Run
para llamar a un método desde un contexto de IU es apropiado (comoDoVariousThingsFromTheUIThreadAsync
).Task.Run
para invocar un método, pero si hay una parte deTask.Run
todo (o casi todo) el código del método, entonces ese es un antipatrón; solo mantenga ese método sincronizado yTask.Run
suba el nivel.Una de las cosas más importantes para recordar al decorar un método con asíncrono es que al menos hay un operador en espera dentro del método. En su ejemplo, lo traduciría como se muestra a continuación usando TaskCompletionSource .
fuente
Cuando usa Task.Run para ejecutar un método, Task obtiene un hilo de threadpool para ejecutar ese método. Desde la perspectiva del subproceso de la interfaz de usuario, es "asíncrono" ya que no bloquea el subproceso de la interfaz de usuario. Esto está bien para la aplicación de escritorio, ya que generalmente no necesita muchos subprocesos para ocuparse de las interacciones del usuario.
Sin embargo, para la aplicación web cada solicitud es atendida por un subproceso de grupo de subprocesos y, por lo tanto, el número de solicitudes activas se puede aumentar guardando dichos subprocesos. El uso frecuente de hilos de agrupación de hilos para simular la operación asincrónica no es escalable para aplicaciones web.
True Async no implica necesariamente el uso de un subproceso para operaciones de E / S, como el acceso a archivos / bases de datos, etc. Puede leer esto para comprender por qué la operación de E / S no necesita subprocesos. http://blog.stephencleary.com/2013/11/there-is-no-thread.html
En su ejemplo simple, es un cálculo puro vinculado a la CPU, por lo que usar Task.Run está bien.
fuente
I should NOT wrap the synchronous call in Task.Run()
eso es correcto. Si lo haces, solo estarías cambiando los hilos. es decir, está desbloqueando el subproceso de solicitud inicial pero está tomando otro subproceso del conjunto de subprocesos que podría haberse utilizado para procesar otra solicitud. El único resultado es una sobrecarga de cambio de contexto cuando la llamada se completa para una ganancia absolutamente cero