Na verdade, assíncrono / espera não é tão mágico. O tópico completo é bastante amplo, mas acho que podemos responder a uma pergunta rápida e completa o suficiente para sua pergunta.
Vamos abordar um simples evento de clique no botão em um aplicativo Windows Forms:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
Eu explicitamente não vou falar sobre o que GetSomethingAsync
está retornando por enquanto. Digamos que isso é algo que será concluído após, digamos, 2 segundos.
Em um mundo tradicional não assíncrono, o manipulador de eventos de clique no botão seria algo parecido com isto:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
Quando você clica no botão no formulário, o aplicativo parece congelar por cerca de 2 segundos, enquanto aguardamos a conclusão desse método. O que acontece é que a "bomba de mensagens", basicamente um loop, está bloqueada.
Esse loop pergunta continuamente no Windows "Alguém fez alguma coisa, como mover o mouse, clicou em alguma coisa? Preciso repintar alguma coisa? Se sim, me diga!" e depois processa esse "algo". Esse loop recebeu uma mensagem de que o usuário clicou em "button1" (ou o tipo equivalente de mensagem do Windows) e acabou chamando nosso button1_Click
método acima. Até que esse método retorne, esse loop ficará parado aguardando. Isso leva 2 segundos e, durante isso, nenhuma mensagem está sendo processada.
A maioria das coisas que lidam com janelas são feitas usando mensagens, o que significa que, se o loop de mensagens parar de bombear mensagens, mesmo por apenas um segundo, ele será rapidamente percebido pelo usuário. Por exemplo, se você mover o bloco de notas ou qualquer outro programa em cima do seu próprio programa e depois se afastar novamente, uma enxurrada de mensagens de tinta será enviada ao seu programa, indicando qual região da janela que agora se tornou visível novamente. Se o loop de mensagens que processa essas mensagens estiver aguardando algo bloqueado, nenhuma pintura será feita.
Portanto, se no primeiro exemplo, async/await
não cria novos threads, como isso acontece?
Bem, o que acontece é que seu método é dividido em dois. Este é um desses tipos de tópicos abrangentes, por isso não entrarei em muitos detalhes, mas basta dizer que o método está dividido nessas duas coisas:
- Todo o código que antecede
await
, incluindo a chamada paraGetSomethingAsync
- Todo o código a seguir
await
Ilustração:
code... code... code... await X(); ... code... code... code...
Reorganizados:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Basicamente, o método é executado assim:
- Ele executa tudo até
await
Ele chama o GetSomethingAsync
método, que faz sua parte, e retorna algo que será concluído em 2 segundos no futuro
Até agora, ainda estamos dentro da chamada original para button1_Click, acontecendo no thread principal, chamado a partir do loop de mensagens. Se o código anterior await
levar muito tempo, a interface do usuário continuará congelada. No nosso exemplo, nem tanto
O que a await
palavra - chave, juntamente com alguma mágica inteligente do compilador, faz é basicamente algo como "Ok, quer saber, eu simplesmente retornarei do manipulador de eventos do clique no botão aqui. Quando você (como em, a coisa que nós ' estou aguardando) chegar ao fim, avise-me porque ainda tenho algum código para executar ".
Na verdade, ele permitirá que a classe SynchronizationContext saiba que está pronto, o que, dependendo do contexto de sincronização real em execução no momento, ficará na fila para execução. A classe de contexto usada em um programa Windows Forms a enfileirará usando a fila que o loop da mensagem está bombeando.
Então, ele volta ao loop de mensagens, que agora está livre para continuar enviando mensagens, como mover a janela, redimensioná-la ou clicar em outros botões.
Para o usuário, a interface do usuário agora é responsiva novamente, processando outros cliques em botões, redimensionando e, o mais importante, redesenhando , para que não pare de congelar.
- Dois segundos depois, o que estamos aguardando é concluído e o que acontece agora é que ele (bem, o contexto de sincronização) coloca uma mensagem na fila que o loop de mensagens está olhando, dizendo "Ei, eu tenho mais código para você executar ", e este código é todo o código após a espera.
- Quando o loop da mensagem chega a essa mensagem, ele basicamente "reinsere" o método de onde parou, logo após
await
e continua executando o restante do método. Observe que esse código é chamado novamente a partir do loop de mensagens; portanto, se esse código fizer algo demorado sem usar async/await
corretamente, ele bloqueará novamente o loop de mensagens
Existem muitas partes móveis por baixo do capô aqui, então aqui estão alguns links para mais informações, eu diria "se precisar", mas esse tópico é bastante amplo e é bastante importante conhecer algumas dessas partes móveis . Invariavelmente, você entenderá que assíncrono / espera ainda é um conceito que vaza. Algumas das limitações e problemas subjacentes ainda vazam para o código circundante e, se não o fizerem, você geralmente acaba depurando um aplicativo que quebra aleatoriamente sem, aparentemente, um bom motivo.
OK, e daí se GetSomethingAsync
gerar um thread que será concluído em 2 segundos? Sim, obviamente existe uma nova discussão em jogo. Esse encadeamento, no entanto, não é devido à assíncrona deste método, é porque o programador deste método escolheu um encadeamento para implementar código assíncrono. Quase todas as E / S assíncronas não usam um encadeamento, elas usam coisas diferentes. async/await
por si só, não ativam novos threads, mas obviamente as "coisas pelas quais esperamos" podem ser implementadas usando threads.
Há muitas coisas no .NET que não necessariamente geram um thread por conta própria, mas ainda são assíncronas:
- Solicitações da Web (e muitas outras coisas relacionadas à rede que levam tempo)
- Leitura e gravação assíncrona de arquivos
- e muitos mais, um bom sinal é se a classe / Interface em questão métodos chamado
SomethingSomethingAsync
ou BeginSomething
e EndSomething
e há uma IAsyncResult
envolvidos.
Geralmente essas coisas não usam um fio debaixo do capô.
OK, então você quer algumas dessas "coisas abrangentes"?
Bem, vamos perguntar ao Try Roslyn sobre o nosso clique no botão:
Experimente Roslyn
Não vou vincular a classe gerada aqui, mas é uma coisa bem sangrenta.