Todos os programas que escrevemos até aqui compartilham uma característica: fazem uma coisa de cada vez, em sequência, do começo ao fim. Cada instrução espera a anterior terminar. Para muitos programas, isso basta. Mas os programas modernos frequentemente precisam lidar com tarefas lentas — baixar algo da internet, ler um arquivo grande do disco, consultar um banco de dados — e, quando enfrentam a abordagem sequencial, surge um desperdício. Enquanto o programa espera a internet responder (o que pode levar segundos, uma eternidade para um computador), ele fica completamente parado, sem fazer nada, quando poderia estar realizando outra coisa. Nesta aula, conheceremos os fundamentos da concorrência — a capacidade de lidar com várias tarefas de forma sobreposta — e a forma elegante do C# de resolver o problema da espera. É um tema que caracteriza a programação moderna, e a última grande capacidade da linguagem que veremos antes do encerramento.
O problema da espera
Sintamos o problema concretamente. Imagine um programa que precisa baixar informações de três sites diferentes. Na abordagem sequencial que você conhece, ele faria assim: pede o primeiro site, espera parado a resposta chegar, depois pede o segundo, espera parado de novo, depois o terceiro. Se cada download leva dois segundos, o programa gasta seis segundos no total — e, durante quase todo esse tempo, ele está ocioso, apenas aguardando as respostas viajarem pela internet, sem usar o processador para nada útil.
O desperdício é evidente. Durante a espera por uma resposta da rede, nenhum trabalho de processamento está acontecendo — os dados estão viajando, o servidor remoto está processando, e o nosso computador está de braços cruzados. Seria muito melhor se, enquanto espera a resposta de um site, o programa pudesse iniciar os outros downloads, ou fazer outra tarefa qualquer, em vez de ficar parado. Aproveitar esse tempo de espera é o que a concorrência permite, e é o problema central que a abordagem assíncrona do C# resolve com elegância.
Uma distinção importante: dois motivos para concorrência
Antes de mostrar a solução, uma distinção que evita confusão. Há dois motivos diferentes para querer fazer várias coisas ao mesmo tempo, e é bom não misturá-los.
O primeiro é aproveitar a espera, o que acabamos de descrever: o programa está esperando algo externo (rede, disco, banco de dados) e poderia usar esse tempo ocioso. Aqui, o problema não é falta de capacidade de processamento — o processador está livre —, é o tempo perdido esperando. Esse é o caso mais comum na programação de aplicações, e é o foco desta aula.
O segundo é dividir trabalho pesado de cálculo entre vários núcleos do processador — por exemplo, processar uma imagem enorme dividindo-a em partes que múltiplos núcleos calculam simultaneamente. Aqui, o objetivo é usar mais poder de processamento ao mesmo tempo. Esse caso (chamado paralelismo) é menos comum no dia a dia e tem suas próprias ferramentas; menciono-o para que você saiba que existe, mas nosso foco é o primeiro caso, a espera, que é o que você encontrará com mais frequência. A pergunta que distingue os dois: "meu programa está esperando algo, ou calculando algo pesado?". Para a espera, a solução é a assincronia, que veremos agora.
A solução: async e await
O C# oferece uma forma notavelmente elegante de lidar com a espera, através de duas palavras-chave: async e await. A ideia é permitir que o programa inicie uma tarefa demorada e, em vez de ficar parado esperando, seja liberado para fazer outra coisa; quando a tarefa termina, o programa retoma de onde parou. O melhor é que o código resultante parece sequencial — linha após linha, legível —, mas por baixo não desperdiça a espera. Vejamos a forma, usando o exemplo de baixar um texto da internet:
// 'async' marca o método como assíncrono (capaz de esperar sem travar).
async Task BaixarEExibirAsync(string url)
{
var cliente = new HttpClient(); // ferramenta para requisições web
// 'await' espera o download, MAS libera o programa durante a espera:
string conteudo = await cliente.GetStringAsync(url);
// Esta linha só executa quando o download terminar,
// mas o programa NÃO ficou travado esperando.
Console.WriteLine($"Baixados {conteudo.Length} caracteres de {url}");
}
Vamos entender as peças. A palavra async, na declaração do método, marca-o como assíncrono — capaz de esperar por tarefas sem travar. A palavra await (em inglês, "aguardar") é o coração: ela "espera" uma tarefa demorada terminar, mas libera o programa durante essa espera, permitindo que ele faça outras coisas em vez de ficar parado. Quando a tarefa (aqui, o download) termina, a execução retoma na linha seguinte, com o resultado em mãos. Repare que o código se lê como sequencial — pede o download, e a linha seguinte usa o resultado —, mas o "esperar" não travou nada. É essa combinação de código legível com espera eficiente que torna o async/await tão elogiado, a ponto de ter sido copiado por muitas outras linguagens de programação.
O tipo Task que aparece (async Task) representa "uma tarefa em andamento" — uma promessa de que algo será concluído no futuro. Por convenção, métodos assíncronos costumam ter o sufixo Async no nome (BaixarEExibirAsync, GetStringAsync), como uma dica visual de que devem ser aguardados com await. Não se preocupe em dominar todas as sutilezas agora; o essencial é entender a ideia: o await permite esperar sem desperdiçar o tempo de espera.
O ganho: esperas que não travam
Onde a assincronia brilha é quando há muitas esperas. Voltando aos três downloads: com a abordagem assíncrona, o programa pode iniciar os três praticamente ao mesmo tempo e aguardar todos juntos, em vez de um após o outro. Em vez de seis segundos (dois por download, em sequência), o total aproxima-se de dois segundos (os três esperando simultaneamente), pois as esperas se sobrepõem em vez de se somarem. O programa não fica mais parado durante cada espera; ele aproveita o tempo. Esse ganho é especialmente importante em programas que atendem muitas requisições ao mesmo tempo — como os sites e serviços que veremos nas aulas finais —, onde a capacidade de esperar por muitas coisas sem travar é o que permite ao programa ser eficiente e responsivo. Sites feitos em C# usam async/await intensamente por essa razão, e quando você criar o seu, nas aulas finais, encontrará essas palavras com frequência.
Uma nota de cautela honesta
Encerro com uma dose de honestidade, no espírito do curso. A concorrência é um tema poderoso, mas também um dos mais sutis da programação, e não espero que você o domine por completo nesta única aula. Escrever código concorrente correto — especialmente quando várias tarefas acessam os mesmos dados ao mesmo tempo — envolve armadilhas que confundem até programadores experientes, e que se estudam com cuidado e prática. Esta aula lhe deu a ideia essencial e o reconhecimento do async/await, o suficiente para entender o que essas palavras significam quando as encontrar e para usar a forma básica em cenários simples de espera. O domínio pleno da concorrência é uma jornada à parte, que você aprofundará conforme construir programas que dela precisem. Por ora, guarde a ideia central — esperar sem desperdiçar o tempo de espera — e o reconhecimento das palavras que a realizam. Isso é uma base sólida sobre a qual construir depois.
A espera deixou de travar o programa. Com async e await, uma operação demorada que depende de algo externo — rede, disco, banco de dados — libera a execução enquanto aguarda, em vez de manter tudo parado. A interface continua respondendo, o servidor continua atendendo, e o código permanece legível, escrito quase como se fosse sequencial.
A distinção entre esperar por algo externo e ocupar processador com cálculo pesado é o que orienta a escolha certa da ferramenta, e a nota de cautela é honesta: concorrência resolve problemas reais e introduz uma classe própria de defeitos, difíceis de reproduzir. Use quando houver motivo, não por hábito.
Fontes e leituras recomendadas
- Programação assíncrona em C# — a introdução oficial ao
async/await; a base desta aula. - async e await — o modelo assíncrono explicado em detalhe.
- O tipo Task — a representação de uma tarefa em andamento.
- Espera assíncrona versus paralelismo — a distinção entre esperar e calcular em paralelo.
- HttpClient — a ferramenta de requisições web usada no exemplo.
Exercícios
Exercício 1
Explique, com suas palavras, o "problema da espera" descrito na aula, usando o exemplo dos três downloads. Por que a abordagem sequencial (esperar um download por vez) é considerada um desperdício?
Ver resposta
✓ Resposta: O "problema da espera" é que, quando um programa precisa de algo lento e externo (como baixar dados da internet), ele fica parado aguardando a resposta, sem fazer nada útil durante esse tempo. No exemplo dos três downloads pela abordagem sequencial, o programa pede o primeiro site e espera parado dois segundos, depois o segundo e espera mais dois, depois o terceiro e mais dois — totalizando seis segundos, quase todos gastos em espera ociosa. É um desperdício porque, durante essas esperas, o processador está livre e poderia estar iniciando os outros downloads ou fazendo outro trabalho, em vez de ficar de braços cruzados aguardando as respostas viajarem pela rede.
Exercício 2
Explique a diferença entre os dois motivos para concorrência: "aproveitar a espera" e "dividir trabalho pesado de cálculo". Para cada um, dê um exemplo de tarefa apropriada, e diga qual dos dois é o foco desta aula.
Ver resposta
✓ Resposta: "Aproveitar a espera" é o motivo de usar concorrência quando o programa está esperando algo externo (rede, disco, banco) e poderia usar esse tempo ocioso — por exemplo, baixar informações de vários sites, aproveitando as esperas simultaneamente. "Dividir trabalho pesado de cálculo" (paralelismo) é usar vários núcleos do processador para calcular algo intensivo mais rápido — por exemplo, processar uma imagem enorme dividindo-a entre núcleos. A diferença é que o primeiro lida com tempo perdido esperando (o processador está livre), e o segundo com falta de capacidade de cálculo (o processador está ocupado). O foco desta aula é o primeiro: aproveitar a espera, resolvido pela assincronia.
Exercício 3
Explique, com suas palavras, o papel das palavras async e await. O que exatamente acontece quando a execução chega a um await de uma tarefa demorada — o programa fica travado esperando, ou não? O que ele faz durante a espera?
Ver resposta
✓ Resposta: A palavra async marca um método como assíncrono, ou seja, capaz de esperar por tarefas demoradas sem travar o programa. A palavra await espera uma tarefa demorada terminar, mas — e este é o ponto central — libera o programa durante a espera em vez de deixá-lo parado. Quando a execução chega a um await de uma tarefa demorada, o programa não fica travado esperando: ele é liberado para fazer outras coisas (como iniciar outras tarefas), e só retoma a linha seguinte ao await quando a tarefa aguardada termina, já com o resultado disponível. Durante a espera, portanto, o programa aproveita o tempo em vez de desperdiçá-lo.
Exercício 4
A aula afirma que o código com async/await "parece sequencial, mas não desperdiça a espera". Explique o que isso significa, e por que essa combinação (legibilidade + eficiência) é considerada uma vantagem do C#.
Ver resposta
✓ Resposta: Significa que o código escrito com async/await tem a aparência de código sequencial comum — as instruções aparecem em ordem, uma após a outra, e a linha depois de um await naturalmente usa o resultado da espera —, o que o torna fácil de ler e entender, como qualquer código linear. Mas, por baixo, o await não deixa o programa parado durante a espera: ele o libera para outras tarefas, aproveitando o tempo. Essa combinação é uma vantagem do C# porque une duas qualidades normalmente em tensão: a legibilidade de código sequencial (fácil de escrever e entender) com a eficiência de não desperdiçar as esperas. Sem o async/await, obter eficiência na espera geralmente exigiria código bem mais complicado e difícil de acompanhar; com eles, ganha-se a eficiência mantendo a clareza.
Exercício 5
Sem código: um colega quer usar async/await para acelerar um cálculo matemático pesado que ocupa o processador o tempo todo, sem nenhuma espera por internet ou disco. Com base na distinção da aula, explique por que a assincronia não ajudaria nesse caso, e a qual dos dois motivos de concorrência esse problema pertence.
Ver resposta
✓ Resposta: A assincronia não ajudaria nesse caso porque o async/await serve para aproveitar o tempo de espera — e um cálculo matemático pesado que ocupa o processador o tempo todo não tem espera nenhuma a ser aproveitada: não há momento ocioso aguardando internet ou disco; o processador está trabalhando continuamente. O await só traz ganho quando existe uma espera que possa ser liberada, e aqui não existe. Esse problema pertence ao segundo motivo de concorrência — "dividir trabalho pesado de cálculo" (paralelismo) —, cuja solução é distribuir o cálculo entre vários núcleos do processador para que trabalhem simultaneamente, e não a assincronia. O colega precisaria de ferramentas de paralelismo, não de async/await.