Introdução: por que a concorrência é necessária em C#?
Os aplicativos modernos cada vez mais exigem a execução de várias operações simultaneamente. O usuário espera que a interface não "trave" ao carregar dados, e que o servidor consiga processar milhares de requisições em paralelo. Em C# (.NET), para resolver esses problemas, utiliza-se concorrência — a capacidade do programa de executar várias tarefas em intervalos de tempo sobrepostos — e multithreading — a implementação da concorrência usando múltiplas threads de execução.
Neste artigo, abordaremos os conceitos-chave: desde os básicos Thread até os modernos async/await e Parallel. Você aprenderá como escolher a ferramenta certa para cada tarefa e evitar erros comuns, como deadlocks e race conditions.
1. Fundamentos: Thread e ThreadPool
Trabalhando com threads via Thread
A classe System.Threading.Thread é a forma mais de baixo nível de criar threads. Cada thread tem sua própria pilha e é executada de forma independente. Exemplo de criação e inicialização de uma thread:
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 }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Thread de trabalho: {i}"); Thread.Sleep(150); } }}Importante: Criar uma nova thread é uma operação cara. Para a criação frequente de threads de curta duração, é melhor usar ThreadPool.
ThreadPool — pool de threads
ThreadPool gerencia um conjunto de threads de trabalho que são reutilizadas. Isso é mais eficiente do que criar Thread manualmente. Exemplo:
ThreadPool.QueueUserWorkItem(state =>{ Console.WriteLine("Tarefa sendo executada no pool de threads"); Thread.Sleep(500); Console.WriteLine("Tarefa concluída");});
Console.WriteLine("Thread principal continua o trabalho");Console.ReadKey();Limitação: Você não pode controlar diretamente a prioridade ou o nome de uma thread do pool. Para um controle mais refinado, use Task.
2. Task e Task Parallel Library (TPL)
Task — abordagem moderna
A partir do .NET 4.0, Task tornou-se a principal ferramenta para operações assíncronas. Ela abstrai as threads e permite trabalhar com resultados de execução. Exemplo:
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Task<int> task = Task.Run(() => ComputeResult(10)); // Pode fazer outro trabalho Console.WriteLine("Aguardando resultado..."); int result = await task; Console.WriteLine($"Resultado: {result}"); }
static int ComputeResult(int value) { Task.Delay(1000).Wait(); // Simula uma operação longa return value * 2; }}Principais vantagens:
- Retorna resultado via
Task<T> - Suporta cancelamento via
CancellationToken - Permite combinar tarefas (ContinueWith, WhenAll, WhenAny)
- Integrado com
async/await
Parallel — para loops paralelos
A classe Parallel simplifica a execução de loops com paralelização. Exemplo:
using System;using System.Threading.Tasks;
class Program{ static void Main()\