Async/Await: O que tem de novo no .NET 8?
Tags: csharp, dotnet, threads, async
Published: 2023-11-17 22:55:59

Habemus .NET 8! 

Esse lançamento traz uma tonelada de novidades. Recomendo muito que você leia esse [blog post](https://learn.microsoft.com/pt-br/dotnet/core/whats-new/dotnet-8) para ter noção de tudo que foi entregue nesse release.

---

**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/)**

---

Dentre centenas de novidades, separei uma interessante referente a feature `async/await` envolvendo do método `ConfigureAwait`.

Porém, antes de continuar recomendo muito a leitura desse post [Async/Await: Task.ConfigureAwait, Deadlock e Pink Floyd](https://dev.to/angelobelchior/taskconfigureawait-deadlock-e-pink-floyd-416g). 
Nele eu destrincho o funcionamento do método `ConfigureAwait`, passando por cenários onde podemos ter sérios problemas de `lock` e explico como podemos evitar isso. 

Dito isto, vamos em frente!

No .NET 7, o método `ConfigureAwait` recebia um parâmetro booleano para indicar se a `task` voltaria para o contexto original ou não. 

```csharp
/// Configura um aguardador para aguardar a Task  
/// continueOnCapturedContext:   
/// true para tentar organizar a continuação de volta ao contexto original capturado; caso contrário, falso.  
/// returns: Um objeto usado para aguardar a tarefa.
public new ConfiguredTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext)  
{  
    return new ConfiguredTaskAwaitable<TResult>(this, continueOnCapturedContext);
}
```

Já no .NET 8 foi criada uma sobrecarga para esse método onde podemos passar um _enum_ chamado `ConfigureAwaitOptions`.

```csharp
/// Configura um aguardador para aguardar a Task 
/// options: Opções usadas para configurar como a espera nesta tarefa vai ser executada.
/// returns: Um objeto usado para aguardar a tarefa.
public new ConfiguredTaskAwaitable<TResult> ConfigureAwait(ConfigureAwaitOptions options)
{
    if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
                     ConfigureAwaitOptions.ForceYielding)) != 0)
    {
        ThrowForInvalidOptions(options);
    }

    return new ConfiguredTaskAwaitable<TResult>(this, options);

    static void ThrowForInvalidOptions(ConfigureAwaitOptions options) =>
        throw ((options & ConfigureAwaitOptions.SuppressThrowing) == 0 ?
            new ArgumentOutOfRangeException(nameof(options)) :
            new ArgumentOutOfRangeException(nameof(options), SR.TaskT_ConfigureAwait_InvalidOptions));
}
```

E quais seriam as opções do `ConfigureAwaitOptions`? Vejamos o código fonte do `enum`:

```csharp
/// Opções para controlar o comportamento durante a espera.
[Flags]
public enum ConfigureAwaitOptions
{
    None = 0x0,
    ContinueOnCapturedContext = 0x1,
    SuppressThrowing = 0x2,
    ForceYielding = 0x4,
}
```

- **None**: Nenhuma opção especificada. **É o mesmo que usar Task.ConfigureAwait(false)**.
- **ContinueOnCapturedContext**: Tenta empacotar a continuação de volta ao SynchronizationContext original ou presente na thread de origem no momento da espera. **É o mesmo que usar Task.ConfigureAwait(true)**.
- **SuppressThrowing**: Evita lançar uma exceção na conclusão da espera de uma tarefa que termina com o  estado TaskStatus.Faulted ou TaskStatus.Canceled.
- **ForceYielding**: Força uma espera em uma tarefa já concluída a se comportar como se a tarefa ainda não tivesse sido concluída, de modo que o método assíncrono atual será forçado a produzir sua execução.

---

Um ponto importante: Esse enum aceita _bitwise combination_, logo podemos ter a combinação de valores, como por exemplo. 
```csharp
task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | 
                    ConfigureAwaitOptions.None);
```
---

Bem, as duas primeiras opções, **None** e **ContinueOnCapturedContext** foram detalhadas nesse [post](https://dev.to/angelobelchior/taskconfigureawait-deadlock-e-pink-floyd-416g) conforme eu disse acima.

Já o **SuppressThrowing** é bem interessante, e acredito que ele vai ser bem útil, afinal, tem certas ocasiões que eu não gostaria de receber uma _exception_ caso a task seja cancelada por um _CancellationToken_ por exemplo. Aliás, para saber mais sobre o _CancellationToken_, tem um post bacanudo pra você: [Async/Await: Para que serve o CancellationToken?](https://dev.to/angelobelchior/asyncawait-para-que-serve-o-cancellationtoken-nm7)

Um exemplo simples:

```csharp
try
{
    var cancellationTokenSource = new CancellationTokenSource(10);
    var cancellationToken = cancellationTokenSource.Token;
    await Task.Delay(10000, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); 
    Console.WriteLine("Finalizou o processo...");
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

Console.ReadLine();
```

Nesse caso acima, passamos `ConfigureAwaitOptions.None` como argumento ao `ConfigureAwait`, sendo assim, passados 10 milisegundos, o _CancellationToken_ notifica o método `Delay` e uma exceção é lançada: "**A task was canceled.**"
Caso troquemos o parâmetro por `ConfigureAwaitOptions.SuppressThrowing`, **o processo é interrompido, mas não lança uma exceção.**. Sendo assim, receberemos a mensagem: "**Finalizou o processo....**". Interessante, não?

Já o `ConfigureAwaitOptions.ForceYielding` é semelhante a `Task.Yield`. Logo, para explicar um, devo explicar o outro. 

Vamos começar com o `Task.Yield`.

O `Task.Yield` é um método que é usado para criar uma pausa em uma execução assíncrona, permitindo que outras tasks na mesma fila de contexto assíncrono tenham a chance de serem executadas. Esse método é muitas vezes utilizado em cenários onde você quer liberar o controle temporariamente para o sistema de execução assíncrona, permitindo que ele execute outras tarefas agendadas.

Ficou confuso? Aqui vai um exemplo simples :)

```csharp
var task1 = MinhaTarefaAssincrona("Tarefa 1");
var task2 = MinhaTarefaAssincrona("Tarefa 2");

await Task.WhenAll(task1, task2);

return;

static async Task MinhaTarefaAssincrona(string nome)
{
    Console.WriteLine($"Início da {nome}");
    
    // Simula uma tarefa assíncrona demorada
    await Task.Delay(1000);
    
    await Task.Yield();
    
    Console.WriteLine($"Fim da {nome}");
}
```

Explicando o código:

Neste exemplo, o programa inicia duas tarefas assíncronas simultaneamente (`MinhaTarefaAssincrona("Tarefa 1")` e `MinhaTarefaAssincrona("Tarefa 2")`). Cada uma dessas tarefas usa `Task.Yield` para criar uma pausa durante a execução, permitindo que outras tarefas na mesma fila de contexto assíncrono tenham a oportunidade de serem executadas.

O método `Task.WhenAll` é usado para esperar que ambas as tarefas sejam concluídas.

E como sabemos que essa pausa de uma `thread` ocorreu? 

Vamos olhar a saída da execução.

```bash
/Volumes/SSD/Git/ConfigureAwait/ConfigureAwait/bin/Debug/net8.0/ConfigureAwait 
Início da Tarefa 1
Início da Tarefa 2
Fim da Tarefa 2
Fim da Tarefa 1
```

Agora, caso a gente remova o `await Task.Yield();` a saída muda:

```bash
/Volumes/SSD/Git/ConfigureAwait/ConfigureAwait/bin/Debug/net8.0/ConfigureAwait 
Início da Tarefa 1
Início da Tarefa 2
Fim da Tarefa 1
Fim da Tarefa 2
```

O que podemos notar avaliando essas saídas é que, quando temos o `await Task.Yield();` **Tarefa 1** é pausada e aguarda a **Tarefa 2** ser finalizada. Removendo o `await Task.Yield();` temos a sequência de execução natural, onde a **Tarefa 1** é a primeira a começar a ser executada e é a primeira a finalizar. Em seguida temos a **Tarefa 2** que é a segunda a ser executada e é a última a finalizar.

Isso demonstra como `Task.Yield` pode ser usado para melhorar a concorrência e a eficiência em certos cenários assíncronos.

Já o `ConfigureAwaitOptions.ForceYielding` tem um comportamento bem parecido, porém com uma opção a mais: como podemos combinar valores, podemos escolher em qual contexto o retorno da pausa vai ser executado. Seria algo como...

```csharp
task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | 
                    ConfigureAwaitOptions.ContinueOnCapturedContext);
```

Mais informações, acesse a documentação [Task.ConfigureAwait Method (System.Threading.Tasks) | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.configureawait?view=net-8.0)

Basicamente é isso :)

Espero que tenham gostado e até a próxima \o/