Pensé que entendía el patrón de espera asíncrona y la Task.Run
operación.
Pero me pregunto por qué en el siguiente ejemplo de código await
no se sincroniza con el hilo de la interfaz de usuario después de regresar de la tarea finalizada.
public async Task InitializeAsync()
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // "Thread: 1"
double value = await Task.Run(() =>
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6
// Do some CPU expensive stuff
double x = 42;
for (int i = 0; i < 100000000; i++)
{
x += i - Math.PI;
}
return x;
}).ConfigureAwait(true);
Console.WriteLine($"Result: {value}");
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6 - WHY??
}
Este código se ejecuta dentro de una aplicación .NET Framework WPF en un sistema Windows 10 con el depurador de Visual Studio 2019 adjunto.
Estoy llamando a este código desde el constructor de mi App
clase.
public App()
{
this.InitializeAsync().ConfigureAwait(true);
}
Quizás no sea la mejor manera, pero no estoy seguro de si esta es la razón del comportamiento extraño.
El código comienza con el hilo de la interfaz de usuario y debe hacer alguna tarea. Con la await
operación y una vez ConfigureAwait(true)
finalizada la tarea, debe continuar en el subproceso principal (1). Pero no lo hace.
¿Por qué?
fuente
Respuestas:
Es algo complicado.
Estás llamando
await
al hilo de la interfaz de usuario, es cierto. ¡Pero! Lo estás haciendo dentroApp
del constructor.Recuerde que el código de inicio generado implícitamente se ve así:
El bucle de eventos, que se utiliza para volver al subproceso principal, se inicia solo como parte de la
Run
ejecución. Entonces, durante laApp
ejecución del constructor, no hay un bucle de eventos. Todavía.Como consecuencia, el
SynchronizationContext
, que es técnicamente responsable del retorno del flujo al hilo principal despuésawait
, estánull
en el constructor de la aplicación.(
SynchronizationContext
se capturaawait
antes de esperar, por lo que no importa que después de finalizarTask
ya haya un valor válidoSynchronizationContext
: el valor capturado esnull
, por lo queawait
continúa la ejecución en un grupo de subprocesos).Entonces, el problema no es que esté ejecutando el código en un constructor, sino que lo está ejecutando en el
App
constructor de S, momento en el cual la aplicación aún no está totalmente configurada para su ejecución. El mismo código enMainWindow
el constructor se comportaría bien.Hagamos un experimento:
El primer resultado da
el segundo
Entonces puede ver que ya
OnStartup
hay un contexto de sincronización. Así que si usted se mueveInitializeAsync()
haciaOnStartup
, se comportará como se espera también.fuente