A palavra-chave async no C# é usada para informar que um método, expressão lambda ou método anônimo deve ser chamado de maneira assíncrona. Simplesmente marcando um método com este modificador, o C# criará uma nova thread de execução para lidar com a tarefa em questão.
Além disso, quando você está chamando um método assíncrono, a palavra-chave await pausará automaticamente a thread atual até que a tarefa seja concluída.
Usando de async e await através de exemplo
Temos duas classes, uma chamada WithAsyncAwait
e outra WithoutAsyncAwait
. Em ambas as classes vamos criar métodos que colocam a thread em pausa por 1, 2 e 3 segundos.
A classe WithoutAsyncAwait
implementa os três métodos de maneira simplificada. Cada método pausa a thread atual por um tempo e depois retorna uma string mostrando o tempo que ficou executando.
using System.Threading; namespace AsyncAwaitSamples { class WithoutAsyncAwait { public string SleepOneSecond() { Thread.Sleep(1_000); return "This process took 1s"; } public string SleepTwoSeconds() { Thread.Sleep(2_000); return "This process took 2s"; } public string SleepThreeSeconds() { Thread.Sleep(3_000); return "This process took 3s"; } } }
A classe WithAsyncAwait
implementa os três métodos usando tasks. Cada método, assim como na classe anterior, pausa a thread atual por um tempo e depois retorna uma Task
contendo a string de retorno com o tempo que ficou executando.
using System.Threading; using System.Threading.Tasks; namespace AsyncAwaitSamples { class WithAsyncAwait { public async Task<string> SleepOneSecondAsync() { return await Task.Run(() => { Thread.Sleep(1_000); return "Done in 1s"; }); } public async Task<string> SleepTwoSecondsAsync() { return await Task.Run(() => { Thread.Sleep(2_000); return "Done in 2s"; }); } public async Task<string> SleepThreeSecondsAsync() { return await Task.Run(() => { Thread.Sleep(3_000); return "Done in 3s"; }); } } }
Para demonstrar o async em funcionando criaremos dois métodos em nossa aplicação, um vai chamar os métodos da classe WithoutAsyncAwait
outro os métodos da classe WithAsyncAwait
.
using System; using System.Diagnostics; using System.Threading.Tasks; namespace AsyncAwaitSamples { class Program { static void Main(string[] args) { WithoutAsyncAwait(); Console.WriteLine(); WhithAsyncAwait(); } public static void WithoutAsyncAwait() { Console.WriteLine("Without Async Await"); var timer = new Stopwatch(); timer.Start(); var withoutAsyncAwait = new WithoutAsyncAwait(); var oneSec = withoutAsyncAwait.SleepOneSecond(); var threeSec = withoutAsyncAwait.SleepThreeSeconds(); var twoSec = withoutAsyncAwait.SleepTwoSeconds(); Console.WriteLine(oneSec); Console.WriteLine(threeSec); Console.WriteLine(twoSec); timer.Stop(); var timeTaken = timer.Elapsed; Console.WriteLine("Time taken: " + timeTaken.ToString(@"m\:ss\.fff")); } public static void WhithAsyncAwait() { Console.WriteLine("Whith Async Await"); var timer = new Stopwatch(); timer.Start(); var whittAsyncAwait = new WithAsyncAwait(); var oneSec = whittAsyncAwait.SleepOneSecondAsync(); var threeSec = whittAsyncAwait.SleepThreeSecondsAsync(); var twoSec = whittAsyncAwait.SleepTwoSecondsAsync(); Console.WriteLine(oneSec.Result); Console.WriteLine(threeSec.Result); Console.WriteLine(twoSec.Result); timer.Stop(); var timeTaken = timer.Elapsed; Console.WriteLine("Time taken: " + timeTaken.ToString(@"m\:ss\.fff")); } } } /* This will exit Without Async Await This process took 1s This process took 3s This process took 2s Time taken: 0:06.010 Whith Async Await Done in 1s Done in 3s Done in 2s Time taken: 0:03.015 */
Como podemos observar, por usarmos o async e await, executamos as tarefas de forma paralela. A tarefa que leva mais tempo (3s) é o tempo total de execução do método e comparando com o execução sequencial, que leva seis segundos, fica visível a vantagem de se usar métodos async.
Convenções de nomenclatura para métodos assíncronos
Você notou a mudança de nome de SleepOneSecond()
para SleepOneSecondAsync()
, mas por que a mudança? Digamos que a nova versão do método ainda se chama SleepOneSecond();
no entanto, o código de chamada foi implementado da seguinte forma:
public async Task<string> SleepOneSecondAsync() { return await Task.Run(() => { Thread.Sleep(1_000); return "Done in 1s"; }); }
Observe que o método foi marcado com a palavra-chave async e o valor de retorno é um objeto Task
. Por esse motivo adicionamos o prefixo Async ao final do método.
Lembre-se de que a palavra-chave await é responsável por extrair o valor de retorno interno contido no objeto Task
e se for usada incorretamente podemos transformar as chamadas assíncronas em síncronas.
using System; using System.Diagnostics; using System.Threading.Tasks; namespace AsyncAwaitSamples { class Program { static void Main(string[] args) { WhithAsyncAwait_Wrong(); } public static async void WhithAsyncAwait_Wrong() { Console.WriteLine("Whith Async Await Wrong"); var timer = new Stopwatch(); timer.Start(); var whittAsyncAwait = new WithAsyncAwait(); var oneSec = whittAsyncAwait.SleepOneSecondAsync(); Console.WriteLine(oneSec.Result); var threeSec = whittAsyncAwait.SleepThreeSecondsAsync(); Console.WriteLine(threeSec.Result); var twoSec = whittAsyncAwait.SleepTwoSecondsAsync(); Console.WriteLine(twoSec.Result); timer.Stop(); var timeTaken = timer.Elapsed; Console.WriteLine("Time taken: " + timeTaken.ToString(@"m\:ss\.fff")); } } } /* This will exit Whith Async Await Wrong Done in 1s Done in 3s Done in 2s Time taken: 0:06.039 */
Note que por usarmos o await em cada chamada, o programa espera cada execução ser completada antes de realizar a próxima. Por conta disso o tempo total é igual ao de quando não usamos os métodos assíncronos.
Métodos assíncronos com múltiplos awaits
É permitido que um único método assíncrono tenha vários contextos de espera em sua implementação e por conta disso o código abaixo perfeitamente aceitável:
public async Task MultipleAwaits() { await Task.Run(() => { Thread.Sleep(1_000); }); Console.WriteLine("Multiple Awaits - Finish 1s"); await Task.Run(() => { Thread.Sleep(3_000); }); Console.WriteLine("Multiple Awaits - Finish 3s"); await Task.Run(() => { Thread.Sleep(2_000); }); Console.WriteLine("Multiple Awaits - Finish 2s"); }
Conclusão async e await
Aqui estão os pontos-chave deste artigo:
• Métodos (assim como expressões lambda ou métodos anônimos) podem ser marcados com a palavra-chave async para permitir que o método trabalhe de maneira paralela.
• Métodos (assim como expressões lambda ou métodos anônimos) marcados com a palavra-chave async serão executados de forma síncrona até que a palavra-chave await seja encontrada.
• Um único método assíncrono pode ter vários awaits.
• Quando a expressão await é encontrada, a chamada é suspensa até que a tarefa await seja concluída. Nesse ínterim, o controle é retornado ao chamador do método.
• A palavra-chave await ocultará o objeto Task
retornado da exibição, parecendo retornar diretamente o valor de retorno subjacente. Métodos sem valor de retorno simplesmente retornam nulo.
• Como convenção de nomenclatura, os métodos que devem ser chamados de forma assíncrona devem ser marcados com o sufixo Async
Qualquer dúvida ou dicas, entre em contato: leandrolt@gmail.com
Principais referências
– Livro: Pro C# 7: With .NET and .NET Core – Andrew Troelsen
– https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
– https://medium.com/@mteixeira.dev_48908/entendendo-as-tasks-no-c-e-o-paradigma-async-await-31b2f17cf807
– http://www.macoratti.net/17/12/c_progassinc2.htm