En realidad, async / waitit no es tan mágico. El tema completo es bastante amplio, pero para una respuesta rápida pero lo suficientemente completa a su pregunta, creo que podemos manejarlo.
Abordemos un simple evento de clic de botón en una aplicación de formularios Windows Forms:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
Voy a explícitamente no hablar de lo que se GetSomethingAsync
está volviendo por ahora. Digamos que esto es algo que se completará después de, digamos, 2 segundos.
En un mundo tradicional, no asíncrono, el controlador de eventos de clic de botón se vería así:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
Cuando hace clic en el botón en el formulario, la aplicación parecerá congelarse durante unos 2 segundos, mientras esperamos que se complete este método. Lo que sucede es que la "bomba de mensajes", básicamente un bucle, está bloqueada.
Este bucle continuamente pregunta a Windows "¿Alguien ha hecho algo, como mover el mouse, hacer clic en algo? ¿Necesito volver a pintar algo? Si es así, ¡dígamelo!" y luego procesa ese "algo". Este bucle recibió un mensaje en el que el usuario hizo clic en "botón1" (o el tipo de mensaje equivalente de Windows) y terminó llamando a nuestro button1_Click
método anterior. Hasta que este método regrese, este ciclo ahora está atascado en espera. Esto lleva 2 segundos y durante esto, no se procesan mensajes.
La mayoría de las cosas que se ocupan de las ventanas se realizan mediante mensajes, lo que significa que si el bucle de mensajes deja de enviar mensajes, incluso por un segundo, el usuario lo nota rápidamente. Por ejemplo, si mueve el bloc de notas o cualquier otro programa encima de su propio programa, y luego lo aleja nuevamente, se envía una ráfaga de mensajes de pintura a su programa que indica qué región de la ventana que ahora de repente se volvió a ver. Si el bucle de mensajes que procesa estos mensajes está esperando algo bloqueado, entonces no se realiza ninguna pintura.
Entonces, si en el primer ejemplo, async/await
no crea nuevos hilos, ¿cómo lo hace?
Bueno, lo que sucede es que tu método se divide en dos. Este es uno de esos temas generales, por lo que no entraré en demasiados detalles, pero es suficiente decir que el método se divide en estas dos cosas:
- Todo el código previo
await
, incluida la llamada aGetSomethingAsync
- Todo el código siguiente
await
Ilustración:
code... code... code... await X(); ... code... code... code...
Reorganizado:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Básicamente, el método se ejecuta así:
- Ejecuta todo hasta
await
Llama al GetSomethingAsync
método, que hace lo suyo, y devuelve algo que completará 2 segundos en el futuro
Hasta ahora todavía estamos dentro de la llamada original a button1_Click, sucediendo en el hilo principal, llamado desde el bucle de mensajes. Si el código anterior await
lleva mucho tiempo, la IU aún se congelará. En nuestro ejemplo, no tanto
Lo que hace la await
palabra clave, junto con un poco de magia inteligente de compilación, es que básicamente es algo así como "Ok, sabes qué, simplemente voy a regresar del controlador de eventos de clic de botón aquí. Cuando (como en, lo que" estoy esperando) para completar, avíseme porque todavía me queda algo de código para ejecutar ".
En realidad, le permitirá a la clase SynchronizationContext saber que está hecho, lo que, dependiendo del contexto de sincronización real que esté en juego en este momento, se pondrá en cola para su ejecución. La clase de contexto utilizada en un programa de Windows Forms lo pondrá en cola usando la cola que está bombeando el bucle de mensajes.
Por lo tanto, vuelve al bucle de mensajes, que ahora es libre de continuar enviando mensajes, como mover la ventana, cambiar su tamaño o hacer clic en otros botones.
Para el usuario, la interfaz de usuario ahora responde de nuevo, procesa otros clics en los botones, cambia el tamaño y , lo que es más importante, vuelve a dibujar , por lo que no parece congelarse.
- 2 segundos después, lo que estamos esperando se completa y lo que sucede ahora es que (bueno, el contexto de sincronización) coloca un mensaje en la cola que está mirando el bucle de mensajes, diciendo "Oye, tengo más código para ejecutar ", y este código es todo el código después de la espera.
- Cuando el bucle de mensajes llega a ese mensaje, básicamente "volverá a ingresar" ese método donde lo dejó, justo después
await
y continuará ejecutando el resto del método. Tenga en cuenta que este código se llama nuevamente desde el bucle de mensajes, por lo que si este código hace algo largo sin usarlo async/await
correctamente, volverá a bloquear el bucle de mensajes
Aquí hay muchas partes móviles debajo del capó, así que aquí hay algunos enlaces a más información, iba a decir "si lo necesita", pero este tema es bastante amplio y es bastante importante conocer algunas de esas partes móviles . Invariablemente, comprenderá que async / await sigue siendo un concepto con fugas. Algunas de las limitaciones y problemas subyacentes todavía se filtran en el código circundante, y si no lo hacen, generalmente terminará depurando una aplicación que se rompe aleatoriamente, aparentemente sin una buena razón.
Bien, ¿y si GetSomethingAsync
gira un hilo que se completará en 2 segundos? Sí, entonces obviamente hay un nuevo hilo en juego. Sin embargo, este subproceso no se debe a la asincronía de este método, sino a que el programador de este método eligió un subproceso para implementar código asincrónico. Casi todas las E / S asíncronas no usan un hilo, usan cosas diferentes. async/await
por sí mismos no generan nuevos hilos, pero obviamente las "cosas que esperamos" pueden implementarse usando hilos.
Hay muchas cosas en .NET que no necesariamente hacen girar un subproceso por sí solas, pero siguen siendo asíncronas:
- Solicitudes web (y muchas otras cosas relacionadas con la red que llevan tiempo)
- Lectura y escritura asíncrona de archivos
- y muchos más, una señal buena es si la clase / interfaz de métodos en cuestión ha llamado
SomethingSomethingAsync
o BeginSomething
e EndSomething
Y hay un IAsyncResult
implicado.
Por lo general, estas cosas no usan un hilo debajo del capó.
OK, ¿quieres algo de ese "tema general"?
Bueno, preguntémosle a Try Roslyn sobre nuestro clic de botón:
Prueba Roslyn
No voy a vincular en la clase completa generada aquí, pero es bastante sangriento.