No entiendo la diferencia entre Task.Wait
y await
.
Tengo algo similar a las siguientes funciones en un servicio ASP.NET WebAPI:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
¿Dónde Get
quedará estancado?
Que podria causar esto? ¿Por qué esto no causa un problema cuando uso una espera de bloqueo en lugar de hacerlo await Task.Delay
?
Task.Delay(1).Wait()
que es lo suficientemente bueno.Task.Delay(1).Wait()
es básicamente exactamente lo mismo queThread.Sleep(1000)
. En el código de producción real rara vez es apropiado.WaitAll
está causando el punto muerto. Vea el enlace a mi blog en mi respuesta para más detalles. Deberías usarawait Task.WhenAll
en su lugar.ConfigureAwait(false)
una sola llamada a un punto muertoBar
oRos
no lo hará, pero debido a que tiene un número enumerable que está creando más de uno y luego espera a todos, la primera barra bloqueará el segundo. Si enawait Task.WhenAll
lugar de esperar todas las tareas, para no bloquear el contexto ASP, verá que el método regresa normalmente..ConfigureAwait(false)
todo el camino hasta el árbol hasta que se bloquea, de esa manera nada está siempre tratando de volver al contexto principal; eso funcionaria. Otra opción sería activar un contexto de sincronización interna. Enlace . Si lo colocaTask.WhenAll
dentroAsyncPump.Run
, bloqueará efectivamente todo el proceso sin que lo necesite enConfigureAwait
ningún lado, pero esa es probablemente una solución demasiado compleja.Respuestas:
Wait
yawait
, aunque son conceptualmente similares, en realidad son completamente diferentes.Wait
se bloqueará sincrónicamente hasta que se complete la tarea. Por lo tanto, el hilo actual está literalmente bloqueado esperando que se complete la tarea. Como regla general, debe usar "async
todo el camino hacia abajo"; es decir, no bloquee elasync
código. En mi blog, entro en los detalles de cómo el bloqueo en código asincrónico causa un punto muerto .await
esperará asincrónicamente hasta que se complete la tarea. Esto significa que el método actual está "en pausa" (se captura su estado) y el método devuelve una tarea incompleta a la persona que llama. Más tarde, cuando seawait
completa la expresión, el resto del método se programa como una continuación.También mencionó un "bloque cooperativo", por el cual supongo que se refiere a una tarea que está
Wait
ejecutando en el hilo de espera. Hay situaciones en las que esto puede suceder, pero es una optimización. Hay muchas situaciones en las que no puede suceder, como si la tarea es para otro planificador, o si ya se inició o si es una tarea sin código (como en su ejemplo de código:Wait
no puede ejecutar laDelay
tarea en línea porque no hay código para ello).Puede encontrar mi
async
/await
introducción útil.fuente
Wait
funciona bienawait
interbloqueos.Wait
bloquea el hilo y no se puede usar para otras cosas.await
código. O eso, o el punto muerto no estaba relacionado con ninguno de los dos y diagnosticaste mal el problema.SynchronizationContext
, por lo que el bloqueo dentro de una solicitud de ASP.NET Core ya no se bloquea.Basado en lo que leí de diferentes fuentes:
Una
await
expresión no bloquea el hilo en el que se está ejecutando. En cambio, hace que el compilador registre el resto delasync
método como una continuación de la tarea esperada. El control luego vuelve a la persona que llama delasync
método. Cuando se completa la tarea, invoca su continuación y la ejecución deasync
método se reanuda donde lo dejó.Para esperar
task
a que se complete un solo , puede llamar a suTask.Wait
método. Una llamada alWait
método bloquea el hilo de llamada hasta que la instancia de clase única haya completado la ejecución. ElWait()
método sin parámetros se utiliza para esperar incondicionalmente hasta que se complete una tarea. La tarea simula el trabajo llamando alThread.Sleep
método para dormir durante dos segundos.Este artículo también es una buena lectura.
fuente
Algunos hechos importantes no se dieron en otras respuestas:
La "espera asíncrona" es más compleja a nivel de CIL y, por lo tanto, cuesta memoria y tiempo de CPU.
Cualquier tarea puede cancelarse si el tiempo de espera es inaceptable.
En el caso de que "async aguarde" no tenemos un controlador para dicha tarea para cancelarlo o monitorearlo.
Usar Task es más flexible que "async aguarda".
Cualquier funcionalidad de sincronización puede ser envuelta por async.
No veo por qué debo vivir con la duplicación de código para el método de sincronización y asíncrono o el uso de hacks.
fuente