Concorrência e Multithreading em C#: de Thread a async/await
Aplicações modernas cada vez mais exigem a execução simultânea de várias tarefas: desde o processamento de requisições de servidores web até cálculos paralelos em programas desktop. Em C# (.NET), existe um poderoso arsenal de ferramentas para isso — desde threads clássicas (Thread) até construções de alto nível como async/await e a biblioteca Task Parallel Library (TPL). Neste artigo, vamos abordar os conceitos-chave de concorrência e multithreading, sua aplicação prática e melhores práticas.
O que são concorrência e multithreading?
Antes de ir para o código, é importante entender a diferença entre esses conceitos:
- Concorrência (Concurrency) — a capacidade do programa de executar várias tarefas, alternando entre elas, criando a ilusão de execução simultânea. Isso pode ser implementado até mesmo em um único núcleo de processador.
- Multithreading — uma forma de concorrência onde várias threads (threads) são executadas fisicamente ao mesmo tempo em um processador multi-core.
- Paralelismo (Parallelism) — um subconjunto do multithreading, onde diferentes partes de uma tarefa são executadas estritamente ao mesmo tempo em núcleos diferentes.
Em C#, você pode usar tanto threads de baixo nível quanto abstrações que gerenciam threads automaticamente. A escolha depende da tarefa: para operações I/O-bound (trabalho com arquivos, rede), async/await é mais adequado, e para CPU-bound (cálculos), Parallel.For ou Task.
Criação e gerenciamento de threads: classe Thread
A classe System.Threading.Thread é a maneira mais baixo nível de trabalhar com threads em C#. Ela oferece controle total, mas requer sincronização cuidadosa.
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start();
// A thread principal continua o trabalho for (int i = 0; i < 5; i++) { Console.WriteLine($"Thread principal: {i}"); Thread.Sleep(100); }
thread.Join(); // Aguarda a conclusão da thread filha Console.WriteLine("Trabalho concluído."); }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Thread filha: {i}"); Thread.Sleep(150); } }}Problemas do gerenciamento manual de threads:
- Complexidade de sincronização (condições de corrida, deadlocks).
- Alta sobrecarga na criação e destruição de threads.
- Falta de suporte embutido para retorno de resultado.
Por isso, no C# moderno, prefere-se usar a Task Parallel Library.
Task Parallel Library (TPL) e async/await
A classe Task do namespace System.Threading.Tasks representa uma operação assíncrona. Em combinação com as palavras-chave async e await, ela permite escrever código assíncrono que se parece com código síncrono.
Exemplo básico com Task
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Console.WriteLine("Início do trabalho.");
// Inicia uma tarefa assíncrona Task<int> task = CalculateAsync(10);
// Enquanto a tarefa é executada, podemos fazer outra coisa Console.WriteLine("Fazendo outro trabalho...");
// Aguarda o resultado int result = await task; Console.WriteLine($"Resultado: {result}"); }
static async Task<int> CalculateAsync(int number) { await Task.Delay(1000); // Simula uma operação longa return number * number; }}