Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Por debaixo do capô: async/await e as mágicas do compilador csharp

Posted on Oct 18 No post anterior eu falei um pouco sobre Açúcar Sintático e como o compilador do csharp trabalha para facilitar nossas vidas.Propositalmente eu deixei de fora a feature async/await, queria fazer algo mais elaborado para explicar como as coisas funcionam por debaixo do capô quando utilizamos métodos assíncronos.O cenário que eu trago aqui é simples e muito comum: Uma classe BlogService que contém um método chamado ObterPostPorIdAsync no qual faz uma requisição assíncrona a uma API, utilizando o HttpClient.Eu escolhi justamente esse cenário porque o método que faz a requisição (GetAsync) e o método que obtém o conteúdo de resposta como string (ReadAsStringAsync) são assíncronos.Nesse ponto eu assumo que você conheça o mínimo sobre async/await, isso é fundamental! Caso contrário, recomendo fortemente estudar o assunto. Esse link da Microsfoft vai te ajudar: https://learn.microsoft.com/pt-br/dotnet/csharp/asynchronous-programming/Abaixo segue nossa classe.Se você já tem um certo conhecimento sobre csharp, consumo de apis etc., não deve ter nenhuma dúvida sobre como esse código funciona.Porém, em resumo, temos:Reforço aqui que meu objetivo é ser didático, por isso não temos validações do response, políticas de retentativas e etc.Se você leu atentamente o post anterior notou que o compilador do csharp ao gerar o código IL acaba por adicionar caracteres especiais em nome de métodos, classes e etc, algo como:Claramente esse código não compila! Porém, ao obter o código gerado pelo compilador através do site https://sharplab.io/ resolvi ajustá-lo para que fosse possível sua execução.Respira fundo! Vai com calma e não se preocupe! Eu vou explicar direitinho o que acontece por debaixo do capô:Sem pânico!Antes de tudo, vamos nos concentrar apenas na classe BlogService! A classe Program e o record Post servem só de apoio!Numa primeira análise notamos o quanto de código o compilador gerou e fica nítido que o foco é que ele seja performático e gere a menor quantidade de alocações possível.Aliás, a complexidade cognitiva do método MoveNext da struct ObterPostPorIdAsyncStateMachine é de 18, sendo que o aceitável é 10.Eu uso um plugin no Rider que me dá essa informação:Mas faço questão de reforçar mais uma vez que esse é o código mais otimizado possível para essa situação. O time de desenvolvimento do compilador do csharp trabalha árduamente para que a cada versão sejam incluídas mais e mais otimizações em todo o processo de compilação!Porém, diferente do Baianinho de Mauá, as mágicas que o compilador faz NÃO SÃO INFINITAS. Se seu código não for minimante bem escrito, não vai adiantar nada, por isso é importante entendermos o que se passa por debaixo do capô!.Vamos ao que interessa!A primeira coisa que precisamos entender é que o processo assíncrono é gerenciado por uma máquina de estados.Dentro da classe BlogService é criada uma struct privada chamada ObterPostPorIdAsyncStateMachine. É nessa struct que toda mágica acontece. Cada método assíncrono existente na classe BlogService teria sua própria máquina de estados. Nesse caso criei apenas um método para fins didáticos.Essa struct implementa a interface IAsyncStateMachine que fica dentro do namespace System.Runtime.CompilerServices e contém os seguintes métodos:Essa interface representa uma máquina de estados geradas para métodos assíncronos e é destinada apenas ao uso do compilador.O método MoveNext() move a máquina de estados para o próximo estado.Já o método SetStateMachine(IAsyncStateMachine stateMachine) seta a máquina de estados com uma réplica alocada na memória heap.Essa máquina tem 4 estados que são armazenados na variável global do tipo int chamada State. Note que dentro do método MoveNext o valor da variável State é copiado para a variável num. Alterar diretamente o valor de uma variável global numa máquina de estados pode ser perigoso e trazer efeitos colaterais indesejados. Essa variável global só vai receber um valor quando seu estado, de fato, mudar.Estado Inicial: State = -1: Ao invocar o método MoveNext pela primeira vez, a máquina de estados é iniciada. É nesse momento onde as variáveis são criadas.Após a criação das variáveis, é solicitada a requisição. Note que eu usei a palavra solicitada e não efetuada, afinal, como é mostrado no código, a variável awaiter está aguardando o processamento...... e com isso, damos início ao segundo estado...Estado Em Execução: State = 0:A variável awaiter tem uma propriedade chamada IsCompleted que indica se o processo está completo ou não (true/false).Como estamos na primeira rodada do processamento (o método MoveNext foi acionado apenas uma vez), esse IsCompleted é falso, fazendo com que o estado (State) fique com o valor 0 e que seja invocado o método Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);.Esse é o ponto chave de todo o fluxo!Esse método recebe o objeto que está aguardando a finalização de um processo (awaiter) e qual objeto que o invocou (this), sendo esse um tipo que implemente a interface IAsyncStateMachine. Tudo via referência.Basicamente o AwaitUnsafeOnCompleted vai esperar por alguma mudança de estado no processamento do método aguardado pelo awaiter (_httpClient.GetAsync(requestUri)) e em seguida invoca o método MoveNext da struct que o invocou (ObterPostPorIdAsyncStateMachine). Temos aqui um looping: Enquanto o awaiter.IsCompleted não for true, State vai ficar como 0 e o método Builder.AwaitUnsafeOnCompleted vai ser invocado novamente.Esse processo todo também vai ocorrer quando estamos lendo o conteúdo do response após a requisição ser efetuada com sucesso:Perceba que o fluxo é exatamente o mesmo!Após os awaiters derem o sinal de que foram executados (IsCompleted == true) passamos para o próximo estado da nossa máquina.Estado Obtendo Resultado: State = 1: Aqui chegamos ao final do nosso processo.Com o json obtido através do response vamos desserializa-lo e criar um objeto Post.É possível notar o uso do go to (que me lembra o saudoso e famigerado VB6: On Error go to Hell).O compilador utiliza o go to de maneira estratégica, fazendo com que o código desvie para o trecho onde o segundo awaiter está: _awaiter2 = _httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter();. Imagine que cada método _async dentro desse processo poderia ter o seu go to justamente para que o código consiga alcançá-lo a partir do momento que o método MoveNext fosse executado. Normalmente usaríamos um if ou quebraríamos o método em várias partes, mas como disse, o compilador escolhe a melhor maneira para ele e não para quem está lendo o código.E se por acaso explodir um erro?Estado Ocorreu um Erro: State = -2: Todo processo é envolvido por um try/catch e caso ocorra um erro, o State é mudado para -2, as variáveis são setadas como nulo e o builder armazena a exception gerada.Ainda temos trechos de código que são utilizados para darem dispose nos objetos. Se você acompanhou o post anterior deve ter notado que a instrução using se torna um try/finally, e é no finally que os objetos são "descartados".Por fim, e não menos importante temos o método: ObterPostPorIdAsync:Esse trecho não tem segredo! É nele onde os valores iniciais são setados e a máquina de estados é criada e inicializada.Se você leu com atenção deve ter notado duas coisas:E é aqui aonde quero chegar: eu quis com esse post mostrar como o compilador lida com o async/await, como que o código é gerado e qual é a estratégia que ele usa para saber quando um processo terminou ou lançou um erro.A classe Task por si só mereceria um post todinho só para ela. Fora que ainda existem outros cenários que eu não cobri aqui, mas cobrirei em breve, como por exemplo o uso maléfico, maligno, molecular e abominável do .Result, CancellationToken em métodos assíncronos, a função do .ConfigureAwait(true/false) e o tratamento de exceções. Quero escrever um post para cada um desses itens dando a devida atenção.Para entender melhor todo o fluxo disponibilizei no github o código fonte!Coloque um break point dentro do método MoveNext e vá navegando linha a linha.Quer ir mais afundo nesse assunto?Esse é, sem dúvidas, um dos melhores posts sobre async/await: https://devblogs.microsoft.com/pfxteam/asyncawait-faq/. E a melhor parte está nos comentários. Leitura obrigatória.Era isso, até a próxima!Não vai embora não!!Vem com a gente turma! Depois de anos e anos colocando sistemas em produção, resolvemos criar um curso completo sobre Clean Architecture e Clean Code com .Net. De Dev pra Dev! Presencial! Na prática! Vagas limitadas!DIA 25 DE NOVEMBRO DE 2023, no Espaço Paulista Promoções de Eventos.Acesse: https://vemcodar.com.br/Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well Confirm For further actions, you may consider blocking this person and/or reporting abuse Shuja3Khan - Oct 18 TechDogs - Oct 18 Jeeanny - Oct 18 Scott Nath - Oct 18 Once suspended, angelobelchior will not be able to comment or publish posts until their suspension is removed. Once unsuspended, angelobelchior will be able to comment and publish posts again. Once unpublished, all posts by angelobelchior will become hidden and only accessible to themselves. If angelobelchior is not suspended, they can still re-publish their posts from their dashboard. Note: Once unpublished, this post will become invisible to the public and only accessible to Angelo Belchior. They can still re-publish the post if they are not suspended. Thanks for keeping DEV Community safe. Here is what you can do to flag angelobelchior: angelobelchior consistently posts content that violates DEV Community's code of conduct because it is harassing, offensive or spammy. Unflagging angelobelchior will restore default visibility to their posts. DEV Community — A constructive and inclusive social network for software developers. With you every step of your journey. Built on Forem — the open source software that powers DEV and other inclusive communities.Made with love and Ruby on Rails. DEV Community © 2016 - 2023. We're a place where coders share, stay up-to-date and grow their careers.



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Por debaixo do capô: async/await e as mágicas do compilador csharp

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×