Depois de lidar com o padrão assíncrono / aguardado do C # por um tempo, de repente percebi que não sabia realmente como explicar o que acontece no código a seguir:
async void MyThread()
{
while (!_quit)
{
await GetWorkAsync();
}
}
GetWorkAsync()
é assumido como retornando um aguardável Task
que pode ou não causar uma troca de encadeamento quando a continuação é executada.
Eu não ficaria confuso se a espera não estivesse dentro de um loop. Eu naturalmente esperaria que o restante do método (ou seja, continuação) fosse executado em outro segmento, o que é bom.
No entanto, dentro de um loop, o conceito de "o resto do método" fica um pouco nebuloso para mim.
O que acontece com "o resto do loop" se o encadeamento é ativado na continuação vs. se não é trocado? Em qual thread a próxima iteração do loop é executada?
Minhas observações mostram (não verificado conclusivamente) que cada iteração começa no mesmo encadeamento (o original) enquanto a continuação é executada em outro. Isso pode realmente ser? Em caso afirmativo, esse é um grau de paralelismo inesperado que precisa ser considerado em relação à segurança de thread do método GetWorkAsync?
ATUALIZAÇÃO: Minha pergunta não é duplicada, como sugerido por alguns. O while (!_quit) { ... }
padrão de código é apenas uma simplificação do meu código real. Na realidade, meu encadeamento é um loop de longa duração que processa sua fila de entrada de itens de trabalho em intervalos regulares (a cada 5 segundos por padrão). A verificação real da condição de encerramento também não é uma simples verificação de campo, conforme sugerido pelo código de amostra, mas sim uma verificação de manipulação de eventos.
Respostas:
Você pode realmente conferir no Try Roslyn . Seu método wait é reescrito na
void IAsyncStateMachine.MoveNext()
classe assíncrona gerada.O que você verá é algo como isto:
Basicamente, não importa em qual tópico você está; a máquina de estado pode retomar adequadamente substituindo seu loop por uma estrutura if / goto equivalente.
Dito isto, os métodos assíncronos não são necessariamente executados em um thread diferente. Veja a explicação de Eric Lippert "Não é mágico" para explicar como você pode trabalhar
async/await
em apenas um tópico.fonte
Em primeiro lugar, Servy escreveu algum código em uma resposta a uma pergunta semelhante, na qual esta resposta se baseia:
/programming/22049339/how-to-create-a-cancellable-task-loop
A resposta de Servy inclui um
ContinueWith()
loop semelhante usando construções TPL sem uso explícito das palavrasasync
-await
chave e ; para responder sua pergunta, considere a aparência do seu código quando seu loop for desenrolado usandoContinueWith()
Isso leva algum tempo para você entender, mas em resumo:
continuation
representa um fechamento para a "iteração atual"previous
representa oTask
estado da "iteração anterior" (isto é, ele sabe quando a 'iteração' termina e é usada para iniciar a próxima ..)GetWorkAsync()
retorna aTask
, isso significaContinueWith(_ => GetWorkAsync())
que retornará a,Task<Task>
portanto, a chamadaUnwrap()
para obter a 'tarefa interna' (isto é, o resultado real deGetWorkAsync()
).Então:
Task.FromResult(_quit)
- seu estado começa comoTask.Completed == true
.continuation
é executado pela primeira vez usandoprevious.ContinueWith(continuation)
continuation
atualizações de fechamentoprevious
para refletir o estado de conclusão_ => GetWorkAsync()
_ => GetWorkAsync()
concluído, "continua com"_previous.ContinueWith(continuation)
- ou seja, chamando ocontinuation
lambda novamenteprevious
foi atualizado com o estado de_ => GetWorkAsync()
modo que ocontinuation
lambda é chamado quandoGetWorkAsync()
retorna.O
continuation
lambda sempre verifica o estado de_quit
que, se_quit == false
não houver mais continuações,TaskCompletionSource
o valor definido é definido como_quit
e tudo está concluído.Quanto à sua observação sobre a continuação sendo executada em um thread diferente, isso não é algo que a palavra-chave
async
/await
faria por você, conforme este blog "As tarefas (ainda) não são threads e o assíncrono não é paralelo" . - https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/Eu sugiro que, de fato, vale a pena dar uma olhada mais de perto no seu
GetWorkAsync()
método com relação à rosqueamento e segurança de threads. Se o seu diagnóstico estiver revelando que ele está sendo executado em um encadeamento diferente como consequência do código assíncrono / de espera repetido, algo dentro ou relacionado a esse método deve estar causando a criação de um novo encadeamento em outro local. (Se isso for inesperado, talvez haja.ConfigureAwait
algum lugar?)fonte
SynchronizationContext
- isso é certamente importante, pois.ContinueWith()
usa o SynchronizationContext para despachar a continuação; de fato, explicaria o comportamento que você está vendo seawait
for chamado em um thread ThreadPool ou em um thread ASP.NET. Uma continuação certamente poderia ser despachada para um thread diferente nesses casos. Por outro lado, a chamadaawait
em um contexto de thread único, como um WPF Dispatcher ou Winforms, deve ser suficiente para garantir que a continuação ocorra no original. thread