.NET -  Criando métodos assíncronos falsos com Task.Run()


 Seria possível criar um invólucro assíncrono usando o método Task.Run() e assim tornar assíncrono um método síncrono ?

Vamos esclarecer um conceito importante : programação assíncrona não é mesma coisa que programação paralela.

Confundir os dois é um erro fácil de cometer pois ambos os conceitos estão associados com o objeto Task

A diferença essencial é que uma operação assíncrona esta relacionada com qual recurso você consome enquanto a execução em paralelo esta relacionada com a quantidade de recursos que você usa.

Vejamos o trecho de código a seguir:

public async Task<string> MinhaTaskAsync(Uri endereco)
{
    return await Task.Run(() =>
    {
        using (var client = new WebClient())
        {
            return client.DownloadString(endereco);
        }

    });
}

Este código tenta converter um método síncrono em algo assíncrono envolvendo a operação com um Task.Run();

Apesar disto o método ainda é executado de forma síncrona, sendo apenas executado em uma thread em segundo plano. Então essa abordagem não melhora a escalabilidade do código, já que você está realmente consumindo mais recursos usando uma thread do pool de threads. Isso é menos escalonável do que executar coisas de forma síncrona, pois você está usando mais recursos do que precisa.

O objetivo principal de Task.Run() é executar código vinculado à CPU de maneira assíncrona. Ele faz isso usando uma thread do pool de threads para executar o método e retornar uma Task para representar a conclusão do método.

Se você envolver o trabalho vinculado a uma operação I/O com  Task.Run(), você estará apenas usando uma nova thread para executar o código de forma síncrona. Ele pode ter uma assinatura semelhante porque está retornando uma Task, mas tudo o que você está fazendo é bloquear uma thread diferente.

Nesse sentido, o uso de Task.Run() cria um método assíncronofalso”. Parece um método assíncrono, mas na verdade é apenas uma simulação ao fazer o processamento em uma thread em segundo plano. Os métodos assíncronos não tratam de threads em segundo plano, mas fazem um uso mais eficiente da thread atual.

Se você deseja obter os benefícios de escalabilidade do código assíncrono para operações vinculadas a tarefas de I/O ou E/S, você precisa implementar o padrão assíncrono usando as palavras-chave async e await. Esses são, na verdade, atalhos para configurar callbacks para operações de longa duração.

Um exemplo do mesmo código executado de forma assíncrona é mostrado a seguir:

public async Task<string> MinhaTaskAsync(Uri endereco)
{
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync(address);
    }
}

Desta forma ao expor uma API com uma assinatura de método assíncrono, você está sinalizando aos consumidores que há benefícios de escalabilidade no uso do método.

Eles usarão o método se estiverem preocupados com a escalabilidade, embora se estiverem mais preocupados com a capacidade de resposta ou paralelismo, eles podem preferir invocar um método síncrono em uma thread de segundo plano.

A questão é que a escolha deve ser deixada para o consumidor. Eles podem implementar o método assíncrono, usar uma continuação para executar algum código assim que a operação for concluída ou apenas executar uma versão síncrona se a escalabilidade não for uma preocupação.

O que você não quer fazer é expor um método assíncrono "falso" que apenas envolve um conjunto de chamadas síncronas. Isso surpreende o usuário, oferecendo a eles algo que não é o que afirma ser, ou seja, um método assíncrono que realmente executa de forma síncrona.

Assim, seja honesto com seus clientes....

"Disse-lhe Jesus: Porque me viste, Tomé, creste; bem-aventurados os que não viram e creram."
João 20:29

Referências:


José Carlos Macoratti