Async/Await: Task.Result e a morte dos Pandas de Madagascar
Published: 2023-11-01 22:10:31



![Desenho de um panda usando um notebook - Essa imagem foi gerada utilizando o Dall-E no Bing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvsqbxmq2g04rih70jw3.jpeg)

Continuando a discussão sobre "Métodos Assíncronos e Deadlocks" que eu comecei no [post anterior](https://dev.to/angelobelchior/taskconfigureawait-deadlock-e-pink-floyd-416g), neste artigo abordarei algumas das situações mais trágicas que acontecem quando o uso do `async/await` é empregado incorretamente, focando especificamente na problemática conhecida como "A morte dos Pandas de Madagascar".

---

**Antes de continuar, #VemCodar com a gente!!**

Tá afim de criar APIs robustas com .NET?

Feito **de dev para dev**, direto das trincheiras, por quem coloca **software em produção há mais de 20 anos**, o curso ["APIs robustas com ASP.NET Core 8, Entity Framework, Docker e Redis"](https://www.sympla.com.br/evento-online/apis-robustas-com-asp-net-core-8-entity-framework-docker-e-redis/2315443?referrer=www.vemcodar.com.br) traz de maneira direta, sem enrolação, tudo que você precisa saber para construir suas APIs de tal forma que elas sejam seguras, performáticas, escaláveis e, acima de tudo, dentro de um ambiente de desenvolvimento totalmente configurado utilizando Docker e Docker Compose. 

Não fique de fora! Dê um Up na sua carreira!

**O treinamento acontecerá nos dias 27/02, 28/02 e 29/02, online, das 19.30hs às 22.30hs**

**Acesse: [https://vemcodar.com.br/](https://vemcodar.com.br/)**

---

> Numa recente pesquisa feita pelo **NID2A** - [_Nogare Institute for Data Analysis and Astrology_](https://diegonogare.net/) - cada vez que é usada a propriedade `.Result` da forma errada em um sistema .Net, morrem ao menos 3 Pandas.

O uso de `async/await` tornou-se uma prática comum no desenvolvimento de software moderno, permitindo que sejam criadas aplicações mais responsivas e eficientes, porém, ainda existem pessoas que não entenderam bem o tamanho do problema quando utilizamos de forma errada a propriedade `.Result` da classe `Task`. 

No [post anterior](https://dev.to/angelobelchior/taskconfigureawait-deadlock-e-pink-floyd-416g) eu falei bastante sobre **deadlocks** (acho que eu citei ao menos **20** vezes essa palavra lá) e agora eu quero continuar esse assunto, porém, explorando um pouco mais a fundo o `.Result`.

## Mas afinal, o que o .Result faz?

O `.Result` é uma maneira de obter o resultado de uma operação assíncrona em C#: 

```csharp
var post = blog.ObterPostPorIdAsync(1).Result;
```

Quando uma tarefa assíncrona é chamada usando `await`, a thread original não é bloqueada, permitindo que outras tarefas sejam executadas. 

No entanto, **usar `.Result` em uma tarefa assíncrona bloqueará a thread original até que a tarefa seja concluída**. E você já deve imaginar o que pode dar errado nessa situação, certo? Não?? Ok, eu explico!

Quando você utiliza o `.Result` em aplicativos com várias _threads_, com grandes quantidades de operações assíncronas, isso pode levar a um uso ineficiente dos recursos do sistema, diminuindo o desempenho e a capacidade de resposta do aplicativo. 

E o pior, caso o `.Result` seja usado em um fluxo onde também temos o uso do `.Wait()` é muito provável que ocorra um **deadlock**. Se a operação assíncrona exigir acesso a um recurso que está sendo bloqueado por outra parte do código, o `.Result` pode criar em um impasse, onde ambas as _threads_ ficam esperando indefinidamente uma pela outra, prejudicando o funcionamento da aplicação.

Fora que o uso do `.Result` também anula um dos principais benefícios do `async/await`: **a concorrência**! A ideia por trás do `async/await` **é liberar a _thread_ original para que ela possa realizar outras tarefas enquanto a operação assíncrona é executada**. Se essa _thread_ está travada, perde-se a capacidade de aproveitar ao máximo esse benefício.

## Alternativas ao uso do .Result

Agora que você entendeu que podemos ter sérios problemas ao usar o `.Result`, abaixo eu cito algumas alternativas. Mas reforço novamente que é preciso ter um entendimento amplo de como as coisas funcionam por debaixo do capô quando falamos de métodos assíncronos. 

Eu tenho alguns [posts que tratam desse assunto](https://dev.to/angelobelchior/series/25190). 

Vamos começar!

**A primeira e principal recomendação é o uso do `await` para aguardar a conclusão de uma operação assíncrona.**  

```csharp
var resultado = await ObterDadosAssincronos();
```

Essa deve ser sempre a primeira opção. Tente organizar seu código para seja possível utilizar essa abordagem!

---

O uso do `.GetAwaiter().GetResult()` é mais seguro do que `.Result` pois esse método permite aguardar o resultado da tarefa sem bloquear a thread orignal. **Dos mares, o mais calmo, dos males, o menor**.

```csharp
var resultado = ObterDadosAssincronos().GetAwaiter().GetResult();
```

Isso permite que a thread que invocou esse método permaneça ativa e responsiva, sem nenhum bloqueio. 

---

Se você estiver em um contexto em que o uso de `await` não é possível (por exemplo, em um método síncrono), você pode usar `Task.Wait` ou `Task.WaitAll` para aguardar a conclusão da task. **No entanto, lembre-se de que, caso exista alguma parte do código que use um `lock` ou um `.Result` a thread original pode ficar travada e isso pode causar problemas de desempenho e o/ou o famigerado `deadlock`**.

```csharp
var task1 = ObterDadosAssincronos().Wait();
//
var taskA = ProcessamentoAssincronosA();
var taskB = ProcessamentoAssincronosB();
var taskC = ProcessamentoAssincronosC();

Task.WaitAll(taskA, taskB, taskC);
```

---

Outra abordagem é usar callbacks com o método `Task.ContinueWith` para tratar o resultado de tarefas assíncronas quando elas forem concluídas. Isso evita bloqueios na thread principal. Porém isso me lembra javascript e, consequentemente,  aquela imagem do Ryu me vem à mente:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szr0ca24bw7kpoyeea3d.jpeg)

```csharp
ObterDadosAssincronos().ContinueWith(task =>
{
    if (task.Status == TaskStatus.RanToCompletion)
    {
        var result = task.Result; // Ok, não se assuste, eu explico mais abaixo :)
    }
}).Wait();
```
Eu não gosto dessa abordagem. Prefiro organizar o código para utilizar o `await`, porém essa é uma alternativa válida. Mas tenha muito cuidado com o [callback hell](http://callbackhell.com/)!

---

**"Mas Angelo, o construtor de uma classe no csharp não permite usar `async/await`,  por isso, só nesse cenário que eu uso o `.Result`. Eu juro!!!!"**

Seria algo como:

```csharp
public class ParserDeArquivo
{
    public string Conteudo { get; }
    
    public ParserDeArquivo(string caminho)
    {
        using var stream = new FileStream(caminho, FileMode.Open);
        using var reader = new StreamReader(stream);
        Conteudo = reader.ReadToEndAsync().Result;
    }
}

var parser = new ParserDeArquivo("arquivo.txt");
```

Ok, não vou dedicar tempo para pensar no motivo pelo qual precisamos invocar um método assíncrono no construtor, mas eu sugiro uma forma de se fazer isso que, além de ser elegante, não mata nenhum Panda!

```csharp
public class ParserDeArquivo
{
    public string Conteudo { get; }

    private ParserDeArquivo(string conteudo)
        => Conteudo = conteudo;
    
    public static async Task<ParserDeArquivo> CriarAsync(string caminho)
    {
        using var stream = new FileStream(caminho, FileMode.Open);
        using var reader = new StreamReader(stream);
        var conteudo = await reader.ReadToEndAsync();
        return new ParserDeArquivo(conteudo);
    }
}

var parser = await ParserDeArquivo.CriarAsync("arquivo.txt");
```

Explicando: Basicamente, é criado um construtor privado, dessa forma não é possível que essa classe seja instanciada por ninguém, a não ser por métodos dentro dela própria.

No lugar do construtor, temos um método estático assíncrono chamado `CriarAsync` onde passamos o caminho do arquivo por parâmetro. Esse método cria a instância da classe através do construtor privado passando o conteúdo do arquivo que foi lido de forma assíncrona como parâmetro.

Viu só? Nem doeu!

## Mas então para que serve o .Result?

Você provavelmente deve ter se perguntado: Se é tão ruim assim, por qual motivo o `.Result` foi criado?

Se a gente ler a [documentação da Microsoft](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=net-7.0), podemos notar que no exemplo apresentado, o `.Result` é utilizado quando temos várias tarefas que precisam ser disparadas ao mesmo tempo pelo método `Task.WaitAll(tasks.ToArray());`.  Esse método aguarda a lista de _tasks_ passadas por parâmetro finalizarem. Feito isso necessitamos usar o `.Result` para obter o valor processado por cada _task_;

Segue um exemplo:

```csharp
var task1 = CalcularValor(1);  
var task2 = CalcularValor(2);  
var task3 = CalcularValor(3);  
  
await Task.WhenAll(task1, task2, task3);  
  
Console.WriteLine($"Valor da tarefa 1: {task1.Result}");  
Console.WriteLine($"Valor da tarefa 2: {task2.Result}");  
Console.WriteLine($"Valor da tarefa 3: {task3.Result}");  
return;  
  
static async Task<int> CalcularValor(int numero)  
{  
  await Task.Delay(1000);  
  return numero * 10;  
}
```

Nesse contexto, só vamos acessar a propriedade `.Result` se, e somente se, as _taks_ estiverem finalizadas. Nesse momento não existe mais concorrência ou paralelismo (no contexto dessa _task_) e por isso é seguro invocar essa propriedade.

Clique [aqui](https://github.com/angelobelchior/TaskResult) para acessar o código fonte do exemplo!

Outro caso onde precisamos utilizar o `.Result` foi mostrado acima com o método `ContinueWith`! Porém nesse caso é necessário validar o status de execução da task. No exemplo, só vamos obter o valor do `.Result` caso o status da task seja `RanToCompletion`, que significa que **"A tarefa concluiu a execução com sucesso"**!

Se você utiliza alguma outra abordagem para tratar de cenários onde é necessário invocar um método assíncrono dentro de um construtor, deixe aí nos comentários :)

---

Agora aquela dica marota!

[David Fowler](https://github.com/davidfowl), que é Distinguished Engineer na Microsoft no time do ASP.NET Core e criador do [SignalR](https://dotnet.microsoft.com/en-us/apps/aspnet/signalr) criou um repositório no Github com boas práticas para o uso do async/await. 

Para acessar clique [aqui](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md).

**Essa documentação é obrigatória. Esse é o tipo de link que você deveria ter salvo nos favoritos!**

---

E chegamos ao final do post. Esse assunto rende bastante e queri trazer mais conteúdos sobre ele no futuro. 

Espero que tenham curtido.

Até a próxima!