Logo

dev-resources.site

for different kinds of informations.

Async/Await: O que tem de novo no .NET 8?

Published at
11/17/2023
Categories
csharp
dotnet
threads
async
Author
angelobelchior
Categories
4 categories in total
csharp
open
dotnet
open
threads
open
async
open
Author
14 person written this
angelobelchior
open
Async/Await: O que tem de novo no .NET 8?

Habemus .NET 8!

Esse lançamento traz uma tonelada de novidades. Recomendo muito que você leia esse blog post para ter noção de tudo que foi entregue nesse release.

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.
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.

/// 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);
}
Enter fullscreen mode Exit fullscreen mode

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

/// 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));
}
Enter fullscreen mode Exit fullscreen mode

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

/// Opções para controlar o comportamento durante a espera.
[Flags]
public enum ConfigureAwaitOptions
{
    None = 0x0,
    ContinueOnCapturedContext = 0x1,
    SuppressThrowing = 0x2,
    ForceYielding = 0x4,
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | 
                    ConfigureAwaitOptions.None);
Enter fullscreen mode Exit fullscreen mode

Bem, as duas primeiras opções, None e ContinueOnCapturedContext foram detalhadas nesse post 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?

Um exemplo simples:

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();
Enter fullscreen mode Exit fullscreen mode

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 :)

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}");
}
Enter fullscreen mode Exit fullscreen mode

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.

/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
Enter fullscreen mode Exit fullscreen mode

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

/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
Enter fullscreen mode Exit fullscreen mode

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...

task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | 
                    ConfigureAwaitOptions.ContinueOnCapturedContext);
Enter fullscreen mode Exit fullscreen mode

Mais informações, acesse a documentação Task.ConfigureAwait Method (System.Threading.Tasks) | Microsoft Learn

Basicamente é isso :)

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

threads Article's
30 articles in total
Favicon
Launch Announcement: TwistFiber - Automate Your Threads Workflow 🚀
Favicon
Mastering ExecutorService Shutdown: Tracking ThreadPool Termination
Favicon
Threads Overhauls Its Search Feature to Improve User Experience and Engagement
Favicon
Concurrency in Python with Threading and Multiprocessing
Favicon
The Benefits of Having More Threads than Cores: Unlocking the Power of Multi-threading in Modern Computing
Favicon
What is Python GIL? How it works?
Favicon
🚀 Edge Detection with Threads and MiniMagick in Ruby 🌄
Favicon
Master OS Concepts 💡 | Understanding Threads, Processes & IPC 🔧
Favicon
Join Our Threads Community for Exclusive Bad Bunny Merch Discussions!
Favicon
Threads-API ist da
Favicon
Connect with NBA YoungBoy Merch on Threads!
Favicon
Estudo de caso: Thread pools e Out-of-memory
Favicon
Paralelismo e Concorrência 102: Java parallel streams na prática
Favicon
Concurrency and Parallelism in Ruby
Favicon
My take on Modeling Large Amounts of HLA Molecules with Ruby
Favicon
In Java what is ConcurrentModificationException? How to avoid it in multi-threading. #InterviewQuestion
Favicon
Threads API is here
Favicon
How Threads and Concurrency Work in Linux Systems
Favicon
Mastering Concurrency in Go: A Comprehensive Guide
Favicon
Is Threads still a thing?
Favicon
Async/Await: O que tem de novo no .NET 8?
Favicon
How Buying Threads Likes Can Skyrocket Your Follower Count
Favicon
Async/Await: Para que serve o CancellationToken?
Favicon
#6 Subgrid, Threads, Clouds Aren't Real, and Intl.Segmenter
Favicon
Threads Video Downloader - Snapthreads.io
Favicon
Demystifying Java Threads for Beginners
Favicon
Explorando o Multitarefa com Threads em Java
Favicon
Você já usou o System.Threading.Channels?
Favicon
Multithreading in Python: the obvious and the incredible
Favicon
Parallelism via Fiber Coroutines

Featured ones: