Chamadas assíncronas usando Async e Await no C#

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

Leave a Reply

Your email address will not be published. Required fields are marked *