Programação assíncrona e concorrente com Task em C#

A programação assíncrona e concorrente são conceitos fundamentais para a criação de aplicações que precisam lidar com múltiplas operações ao mesmo tempo, como quando é necessário fazer requisições de rede, realizar processamento paralelo ou executar tarefas pesadas sem bloquear a interface do usuário. Em C#, o conceito de Task é uma peça central para implementar essas funcionalidades de maneira eficiente.

O que é uma Task?

Uma Task é como uma tarefa que pode ser executada de forma independente, sem bloquear o restante do programa. Ela é usada para fazer tarefas em segundo plano enquanto o programa continua executando. Você pode esperar o resultado de uma Task usando o comando “await” ou controlar o que acontece depois dela com métodos como “ContinueWith” ou “WaitAll”.

Usando Task, conseguimos evitar que a “thread principal” (a parte do programa que controla a interface com o usuário) fique bloqueada. Isso permite que o programa faça várias ações ao mesmo tempo de maneira mais eficiente, sem travar ou demorar para responder.

Task.Run: iniciando tarefas em uma thread separada

O método “Task.Run” é utilizado para iniciar uma tarefa em uma thread separada. Isso é particularmente útil quando você precisa evitar o bloqueio da thread principal da aplicação, especialmente em aplicações com interface gráfica (GUI), onde o bloqueio da thread principal pode levar a uma interface congelada.

				
					Console.WriteLine("Iniciando tarefa...");
Task.Run(() =>
{
    // Simulando uma operação pesada
    System.Threading.Thread.Sleep(2000);
    Console.WriteLine("Tarefa concluída.");
});

Console.WriteLine("Aguardando a tarefa...");
Console.ReadLine();

				
			
No exemplo acima, a tarefa que simula uma operação pesada (sleep de 2 segundos) é executada em uma thread separada, permitindo que o código continue executando sem bloquear a aplicação.

Task.Delay: criando atrasos assíncronos

Em alguns casos, é necessário inserir um atraso na execução de uma tarefa sem bloquear a thread principal. Isso é facilmente feito com o método “Task.Delay”, que permite aguardar de forma assíncrona por um tempo especificado.

				
					Console.WriteLine("Iniciando atraso...");
await Task.Delay(3000); 
Console.WriteLine("Atraso concluído.");

				
			

Neste exemplo, a tarefa aguarda por 3 segundos sem bloquear a thread principal. O uso de “await” permite que o código continue executando enquanto aguarda o atraso.

Task: tarefas com retorno de valor

Quando você precisa que uma tarefa assíncrona retorne um valor, você pode usar Task<T>. Essa classe permite que você execute uma operação assíncrona que retorna um valor do tipo T, que pode ser qualquer tipo de dado como “int”, “string”, ou até mesmo objetos complexos.

				
					public static async Task Main()
{
    Console.WriteLine("Iniciando tarefa de busca ...");
    string resultado = await BuscarUsuario("José"); 
    Console.WriteLine($"Usuário: {resultado} encontrado.");
}

public static async Task<string> BuscarUsuario(string nome)
{
    Console.WriteLine($"Buscando {nome} no banco...");
    await Task.Delay(3000); 
    return "José Santos"; 
}

				
			

Neste exemplo, o método “BuscarUsuario” é uma tarefa assíncrona que retorna um valor do tipo “string”. A operação de busca é realizada após um atraso simulado de 3 segundos, e o valor retornado é o resultado da busca no “banco“.

Task.WaitAll: aguardando múltiplas tarefas

Quando você tem várias tarefas em execução simultaneamente e precisa aguardar que todas terminem antes de prosseguir, o método “Task.WaitAll” é ideal. Ele permite aguardar a conclusão de várias tarefas de uma vez.

				
					public static async Task Main()
{
    Console.WriteLine("Iniciando múltiplas tarefas...");
    Task task1 = Task.Run(() => ProcessaTarefa(1));
    Task task2 = Task.Run(() => ProcessaTarefa(2));
    Task task3 = Task.Run(() => ProcessaTarefa(3));

    await Task.WhenAll(task1, task2, task3); 

    Console.WriteLine("Todas as tarefas concluídas.");
}

public static void ProcessaTarefa(int id)
{
    System.Threading.Thread.Sleep(2000);
    Console.WriteLine($"Tarefa {id} concluída.");
}

				
			

Neste exemplo, três tarefas são executadas simultaneamente. Usando “Task.WhenAll”, a execução do código só prossegue após todas as tarefas terem sido completadas.

Task.ContinueWith: encadeando tarefas

O método “Task.ContinueWith” permite encadear a execução de uma tarefa após a conclusão de outra. Isso é útil quando você precisa realizar uma série de operações assíncronas em sequência.

				
					Console.WriteLine("Iniciando tarefa...");
Task.Run(() =>
{
    // Simulando tarefa inicial
    System.Threading.Thread.Sleep(2000);
    Console.WriteLine("Tarefa 1 concluída.");
})
.ContinueWith(task =>
{
    // Tarefa a ser executada após a primeira
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine("Tarefa 2 concluída.");
});

Console.WriteLine("Aguardando tarefas...");
Console.ReadLine();

				
			

Neste exemplo, a segunda tarefa será executada assim que a primeira for concluída, sem a necessidade de aguardar explicitamente a primeira tarefa usando “await”.

Task vs async/await: quando usar cada abordagem

Embora “Task” seja uma abstração importante para programação assíncrona, o “async/await” é uma maneira mais moderna e legível de lidar com operações assíncronas. A principal diferença entre os dois é que “async/await” proporciona uma sintaxe mais simples e menos propensa a erros ao escrever código assíncrono.

Quando usar Task:

  • Quando você precisa de mais controle sobre o comportamento da tarefa, como no caso de encadear tarefas ou esperar por múltiplas tarefas.
  • Quando a operação assíncrona não precisa ser aguardada diretamente no método.

Quando usar async/await:

  • Quando você deseja uma sintaxe mais simples e legível.
  • Quando a operação assíncrona precisa ser aguardada diretamente no fluxo do método.

Em muitas situações, você pode combinar as duas abordagens. Por exemplo, você pode usar “Task.Run” para executar algo em segundo plano e, dentro de um método “async”, aguardar essa tarefa com “await”.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

O uso de Task no C# permite que desenvolvedores criem aplicativos mais rápidos e responsivos ao evitar o bloqueio de threads e executar operações em segundo plano. Técnicas como “Task.Run”, “Task.Delay”, “Task.WaitAll” e “Task.ContinueWith” são ferramentas muito úteis para gerenciar tarefas assíncronas e concorrentes. Quando combinado com “async/await”, você pode escrever código assíncrono de forma clara e eficiente, garantindo uma melhor experiência para o usuário.