Já está disponível para todos uma versão inicial do que teremos no C# 5.0, e o foco é programação assíncrona. É muito provável que essa seja a única grande funcionalidade da próxima versão da linguagem. O time do C# entendeu que a programação paralela e o bloqueio de threads são preocupações que ficarão cada vez mais importantes, até porque estamos cada vez com mais núcleos nos computadores, mas o clock dos processadores não sobe faz tempo, e não dá sinais de que vá subir.

Já é possível programar sem bloquear desde a primeira versão do .NET e do C#: ambos nasceram com isso já pronto. O que está sendo feito agora é facilitar, e muito, essa programação, como vocês verão a seguir. A ideia é não deixar o código cheio de “sujeira”, se é que podemos chamar assim, resultado da programação assíncrona. Quem já programou com isso sabe que usar threads, delegates, async results, BeginXYZ, EndXYZ, XYZCompleted, não é exatamente divertido, muitas vezes deixando a lógica de negócios toda quebrada em vários métodos. Se você já programou com Silverlight sabe a dor que é isso, ainda que o uso de lambdas facilite muito. Aqui um exemplo:

public void SumPageSizesAsync(IList<Uri> uris) {
    SumPageSizesAsyncHelper(uris.GetEnumerator(), 0);
}

private void SumPageSizesAsyncHelper(IEnumerator<Uri> enumerator, int total) {
    if (enumerator.MoveNext()) {
        statusText.Text = string.Format("Found {0} bytes ...", total);
        var client = new WebClient();
        client.DownloadDataCompleted += (sender, e) => {
            SumPageSizesAsyncHelper(enumerator, total + e.Result.Length);
        };
        client.DownloadDataAsync(enumerator.Current);
    }
    else {
        statusText.Text = string.Format("Found {0} bytes total", total);
        enumerator.Dispose();
    }
}

Nesse exemplo, estamos calculando o total de bytes de uma lista de urls, de forma assincrona. Aí temos o exemplo da lambda nos ajudando, sendo passada para o evento DownloadDataCompleted. Não é.tão ruim, mas isso começa a ficar pior quando aparece várias vezes. Pior ainda a obrigação do uso de um método auxiliar.

Pra ficar evidente a diferença com o Async CTP, vejam como esse código fica nesta nova versão:

public async Task<int> SumPageSizesAsync(IList<Uri> uris) {
    int total = 0;
    foreach (var uri in uris) {
        statusText.Text = string.Format("Found {0} bytes ...", total);
        var data = await new WebClient().DownloadDataAsync(uri);
        total += data.Length;
    }
    statusText.Text = string.Format("Found {0} bytes total", total);
    return total;
}

É evidente que fica muito mais fácil. Não precisamos mais do método auxiliar para fazer a soma dos totais, ela é feita de forma natural. Na prática, esse código parece igualzinho ao código que faríamos se ele fosse um código síncrono, com uma diferença: a palavra “await” antes da chamada do método DownloadDataAsync.

O que está acontecendo é mais ou menos isso: Durante a compilação, o novo compilador do C#, ao encontrar a declaração await, quebra o método em pedaços. Ao encontrar o primeiro await, ele de imediato retorna a quem chamou o método, ou seja, quem quer que está esperando o total do tamanho das páginas. Só que ele não retorna esse total ainda, ele retorna uma tarefa, usando o tipo Task (veja que o retorno é uma Task<int>). O chamador sabe que essa tarefa provavelmente está em andamento. De volta ao método SumPagesSizesAsync, ele segue fazendo a chamada ao método DownloadDataAsync de forma assíncrona, como se fosse em outro método que retorna um sinalizador de espera, e seguindo para o próximo item do foreach. Ao encontrar o próximo await, ele não precisa retornar ao chamador original, porque esse já recebeu um retorno no primeiro encontro com o await. Ele pega o próximo sinalizador de espera e continua.

Quando o evento “DownloadDataCompleted” é chamado, ele segue com a soma do total, e sinaliza que terminou. Quando todo o foreach foi executado, ele passa a esperar que todos os sinalizadores de espera retornados terminem. Quando todos os sinalizadores concluírem ele segue para a linha depois do foreach, onde o total é atribuído ao texto do textbox.

Só que você não vê nada disso, só vê o belo método acima, limpo. Você diz ao compilador o que espera que ele faça, não como espera que ele faça. Toda a ligação com do método “DownloadDataAsync” com o evento “DownloadDataCompleted“ é feita por convenção baseada no nome, e o compilador funciona por padrão com o modelo anterior já em uso no .NET Framework, ou seja, aproveitamos tudo que já funcionava.

E para fazer um método como o “DownloadDataAsync”, ou seja, que demora, basta usar outra palavra-chave, “async”. Veja um exemplo;

public async Task<string> ConcatAsync(string a, string b) {
    await Task.Delay(10000);
    return a+b;
}

Esse exemplo é bem mais simples, mas já fica bem mais evidente que estamos esperando 10 segundos. Note a palavra async na declaração, e o await no corpo do método.

Uma coisa estranha é que, se você tentar debugar o que está acontecendo, os retornos dos métodos ficam super confusos. Você retorna quando bate no async, depois o código fica pulando entre os pedaços assíncronos, principalmente quando estamos em um laço de foreach. Quem já trabalhou com código assíncrono sabe como isso é divertido, no caso deste CTP é ainda mais interessante.

O legal é que isso tudo está disponível também no Framework, não só no C#. Por isso, o VB.NET também já faz uso das novidades, assim como qualquer outra linguagem do .NET poderá fazer.

Esse modelo pluga perfeitamente também no LINQ, permitindo que esperemos o retorno de várias chamadas assíncronas.

Você já pode experimentar isso tudo, e já está compatível com VS 2010 SP1. Veja aqui a página de async do projeto, baixe ela aqui, e a aqui um documento bem interessante explicando tudo. Usem, experimentem, e me contem o que acharam. Boa diversão.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.