Cada vez mais estamos utilizando o conceito de programação assíncrona. Agora com as facilidades do async e await presentes desde do .Net Framework 4.5, podemos melhorar a experiência do usuário com pequenas alterações no nosso código.
Não pretendo explicar muito sobre isso pois muitos outros já fizeram isso. Como esse artigo bem esclarecedor no site da Microsoft ou até mesmo, esse outro do Marcoratti.
Perceba que o tempo de resposta é pouco mais de 10 segundos. Porque? Porque o método TrabalhoQuePrecisaExecuta(), rodou na mesma thread que as outras operações. E quando fazemos isso, estamos enfileirando as execuções.
Podemos fazer isso (vale lembrar que não precisamos do async e await para usar essa abordagem. O que estamos fazendo aqui, é apenas isolando nosso código em uma thread a parte):
Perceba que o simples fato de jogar o TrabalhoQuePrecisaExecutar() dentro de um Task.Run, deixamos de enfileirar esse processamento e nosso Action simplesmente passou por por essa operação, mandou executa-la em outra thread e seguiu com nosso código.
Não pretendo explicar muito sobre isso pois muitos outros já fizeram isso. Como esse artigo bem esclarecedor no site da Microsoft ou até mesmo, esse outro do Marcoratti.
Então, mão na massa
Nesse primeiro caso, quero exemplificar uma situação comum, onde precisamos executar nosso Action e dentro do Action quero executar uma ação que não impacta na UI, mas que deve ser executada (um registro de log, por exemplo). Então, ao invés de:Podemos fazer isso (vale lembrar que não precisamos do async e await para usar essa abordagem. O que estamos fazendo aqui, é apenas isolando nosso código em uma thread a parte):
Mas e quando eu quero que meu método async retorne algo para a UI?
Bom, vamos pensar... Se eu quero que um método que está em outra thread, retorne algo para minha UI, eu preciso obrigatoriamente, esperar esse método carregar e fatalmente vou cair na mesma situação exposta no primeiro caso:
Ué, então não existe mágica?
Não existe :(
Mas, existe o conceito de estratégia, em grego strateegia, em latim strategi, em francês stratégie...
O que sabemos?
- Se jogarmos nosso código em uma thread separada, isolamos o processamento.
- Se usarmos o Result, podemos recuperar o retorno (isso causa block).
Então, se eu deixar meu código dentro de uma thread logo no início (sabendo que ele demora para executar), seguir com o processamento e pedir o resultado da thread no final, vou "processar em paralelo e reduzir o tempo de resposta". Assim:
Lol, faz sentido O.o !
Mas, e onde entra o async e o await em toda essa história?
Certamente você leu o artigo do Marcoratti e da Microsoft que coloquei no inicio do post (eu sei que você nem clicou), então, deve ter percebido que o async e o await, permitem que eu execute Tasks uma após a outra, mas ainda em paralelo. O próximo teste, usar o código acima, mas vou chamar o método TrabalhoQuePrecisaExecutar() 2 vezes dentro de uma outra thread e utilizar o async e await para paralelizar as duas execuções e somar os retornos. E a thread principal, no result (que vai bloquear a execução na thread principal), vai pegar a soma dessas duas operações. ou seja:
Nos exemplos onde usamos o .Result, perceba que ele tem o mesmo efeito que o await, pois trava o processamento até conseguir recuperar o retorno da thread.
Para colher todos os frutos dessa abordagem, é interessante trabalhar com esse backend em aplicações SPA. Assim podemos utilizar chamadas assíncronas (com ajax) sem bloquear a UI do usuário em momento algum.
Nos exemplo acima, a partir do momento que usamos um controller e um action para retornar os dados em uma View tradicional, estamos bloqueado, porém, usando algumas abordagens para otimizar o tempo de resposta usando os conceitos de programação assíncrona.
Dentro de um controller, por vezes somos obrigados a chamar alguma API externa e acabamos por cair nessa situação. Para essas situações onde a requisição é síncrona e uma implementação dentro dela necessita de uma chamada assincrona, usamos o .Result para garantir o retorno. Não é o melhor cenário, mas para essa situação, em implementação pontual, resolve sem ter de refatorar toda a aplicação.