Siento que la respuesta a esto se debe a que tengo un concepto incorrecto de cómo funcionan los hilos, pero aquí va.

private void button1_Click(object sender, EventArgs e)
{
  this.TestMethodAsync();   // No await, i.e. fire and forget

  // ** Some code here to perform long running calculation (1) **
}

private async Task TestMethodAsync()
{
  // Some synchronous stuff

  await Task.Delay(1000);

  // ** Some code here to perform long running calculation (2) **
}

En primer lugar, no "dispararía y olvidaría" un método asincrónico como este (usaría Task.Run) pero me encontré con un código que sí lo hace y estoy tratando de entender cuál es el efecto.

En una aplicación WinForms, que usa un WindowsFormsSynchronizationContext, mi comprensión de async y await me dice que cuando hago clic en button1, el método se iniciará sincrónicamente en el hilo de la interfaz de usuario. Llamará a TestMethodAsync y se ejecutará sincrónicamente hasta que llegue a la espera. Luego capturará el contexto, iniciará la tarea Task.Delay y cederá el control a la persona que llama. Como no estamos esperando esta llamada, button1_Click continuará en el hilo de la IU y comenzará a realizar el cálculo (1).

En algún momento, Task.Delay(1000) se completará. Luego, una continuación ejecutará el resto del método TestMethodAsync usando el contexto capturado, que en este caso significa que la continuación se ejecutará en el subproceso de la interfaz de usuario. Esto ahora comenzará a realizar el cálculo (2).

Ahora tenemos dos secciones separadas de código que desean ejecutarse en el mismo hilo (el hilo de la interfaz de usuario) al mismo tiempo. Mis investigaciones sobre esto parecen sugerir que el hilo alterna entre las dos secciones de código para realizar ambas.

PREGUNTA:

Estoy confundido acerca de lo que está pasando aquí exactamente. ¿Cómo es posible reanudar en un hilo que ya está ejecutando otro código? ¿Qué obliga al hilo a cambiar entre las dos secciones de código que se quieren ejecutar? En general, ¿qué sucede cuando intenta reanudar en un hilo que ya está ejecutando algún otro código?

(Supongo que esto no es diferente a cómo se ejecuta mi evento de clic en el hilo de la interfaz de usuario en primer lugar, por lo que sé que se ejecuta en el hilo de la interfaz de usuario, y sé que el hilo de la interfaz de usuario también está haciendo otras cosas, pero Realmente no lo había pensado así antes).

2
bornfromanegg 13 nov. 2017 a las 20:26

2 respuestas

La mejor respuesta

Este es el secreto que no entiendes: te doy el Bucle de mensajes de Windows

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    while(TRUE)
    {
        bRet = GetMessage(&msg, NULL, 0, 0);
        if (bRet <= 0) break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

Este es el "principal" real de su aplicación; simplemente no lo ve porque está escondido detrás de escena.

No se podía imaginar un bucle más simple. Recibe un mensaje de la cola. Si no hay más mensajes, el programa debe estar terminado. Si había un mensaje, ejecuta las traducciones de mensajes estándar y envía el mensaje, y luego sigue ejecutándose.

¿Cómo es posible reanudar en un hilo que ya está ejecutando otro código?

No lo es. "Reanudar en un hilo que está ejecutando otro código" es en realidad poner un mensaje en la cola . Ese "otro código" está siendo llamado sincrónicamente por DispatchMessage. Cuando termina, vuelve al bucle, se consulta la cola y el mensaje indica qué código debe enviarse a continuación. Eso luego se ejecuta sincrónicamente hasta que regresa al ciclo.

¿Qué obliga al hilo a cambiar entre las dos secciones de código que se quieren ejecutar?

Nada. Eso no pasa.

En general, ¿qué sucede cuando intenta reanudar en un hilo que ya está ejecutando algún otro código?

El mensaje que describe qué continuación debe ejecutarse está en cola.

Supongo que esto no es diferente de cómo se ejecuta mi evento de clic en el subproceso de la interfaz de usuario en primer lugar, por lo que sé que se ejecuta en el subproceso de la interfaz de usuario, y sé que el subproceso de la interfaz de usuario también está haciendo otras cosas, pero yo Realmente no lo había pensado así antes.

Empiece a pensar en ello.

Los eventos de clic son exactamente iguales. Su programa está haciendo algo; haces clic con el mouse; el controlador de clic no interrumpe el hilo de la interfaz de usuario y comienza a ejecutar un nuevo trabajo en él. Por el contrario, el mensaje se pone en cola y, cuando el control del hilo de la interfaz de usuario vuelve al bucle de mensajes, el clic finalmente se procesa; DispatchMessage hace que Button1_OnClick se invoque a través de algún mecanismo en Windows Forms. Eso es lo que es WinForms ; un mecanismo para traducir los mensajes de Windows en llamadas a métodos de C #.

Pero eso ya lo sabías . Usted sabe que cuando un programa controlado por eventos realiza una operación síncrona de larga duración, la interfaz de usuario se congela, pero los eventos de clic se procesan finalmente. ¿Cómo pensaste que pasó? Debes haber entendido en algún nivel que estaban en cola para procesarlos más tarde, ¿verdad?

Ejercicio: ¿Qué hace DoEvents?

Ejercicio: dado lo que ahora sabe: ¿qué podría salir mal si llama a DoEvents en un bucle para desbloquear su interfaz de usuario?

Ejercicio: ¿En qué se diferencia await de DoEvents en una aplicación GUI?

8
Eric Lippert 13 nov. 2017 a las 18:41

¿Cómo es posible reanudar en un hilo que ya está ejecutando otro código?

Debe estar diseñado específicamente para admitirlo. Es necesario que exista algún marco que permita al hilo tomar trabajo y luego ejecutar ese trabajo en algún momento posterior.

Así es como funciona tu hilo de UI. Tiene una cola, y cada vez que programa un trabajo en el hilo de la interfaz de usuario, agrega un elemento al final de la cola. Luego, el subproceso de la interfaz de usuario toma el primer elemento de la cola, lo ejecuta y, cuando termina, pasa al siguiente elemento, y así sucesivamente, hasta que finaliza la aplicación.

¿Qué obliga al hilo a cambiar entre las dos secciones de código que se quieren ejecutar?

Nada, porque no hace eso. Ejecuta uno, luego, cuando termina, ejecuta el otro.

En general, ¿qué sucede cuando intenta reanudar en un hilo que ya está ejecutando algún otro código?

O alguien escribió un código personalizado para hacer específicamente eso, en cuyo caso, hace lo que ese código le dijo específicamente que hiciera, o usted no puede.

3
Servy 13 nov. 2017 a las 18:13