Pensei entender o padrão de espera assíncrona e a Task.Run
operação.
Mas estou me perguntando por que, no exemplo de código a seguir, await
ele não é sincronizado novamente com o thread da interface do usuário depois de retornar da tarefa concluída.
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??
}
Esse código é executado em um aplicativo .NET Framework WPF em um sistema Windows 10 com o Visual Studio 2019 Debugger anexado.
Estou chamando esse código do construtor da minha App
classe.
public App()
{
this.InitializeAsync().ConfigureAwait(true);
}
Talvez não seja o melhor caminho, mas não tenho certeza se esse é o motivo do comportamento estranho.
O código começa com o thread da interface do usuário e deve executar algumas tarefas. Com a await
operação e ConfigureAwait(true)
após a conclusão da tarefa, ela deve continuar no thread principal (1). Mas isso não acontece.
Por quê?
fonte
Respostas:
É uma coisa complicada.
Você está chamando
await
no thread da interface do usuário, é verdade. Mas! Você está fazendo isso dentroApp
do construtor.Lembre-se de que o código de inicialização gerado implicitamente se parece com o seguinte:
O loop de eventos, usado para retornar ao thread principal, é iniciado apenas como parte da
Run
execução. Portanto, durante aApp
execução do construtor, não há loop de eventos. Ainda.Como conseqüência, o
SynchronizationContext
, que é tecnicamente responsável pelo retorno do fluxo ao encadeamento principal depoisawait
, estánull
no construtor do aplicativo.(
SynchronizationContext
é capturadoawait
antes de aguardar, portanto, não importa que, depois de terminarTask
, já exista um válidoSynchronizationContext
: o valor capturado énull
, portanto,await
continua a execução em um encadeamento do conjunto de encadeamentos.)Portanto, o problema não é que você esteja executando o código em um construtor; o problema é que você esteja executando-o no
App
construtor; nesse momento, o aplicativo ainda não está totalmente configurado para execução. O mesmo código noMainWindow
construtor se comportaria bem.Vamos fazer um experimento:
A primeira saída fornece
o segundo
Então você pode ver que já
OnStartup
existe um contexto de sincronização. Então, se você se moverInitializeAsync()
emOnStartup
, ele irá se comportar como você esperaria dele.fonte