C# - Programação assíncrona e paralela com Task
Hoje vamos falar sobre a classe Task que representa uma operação assíncrona e o seu uso nas operações simultâneas em C#. |
A programação concorrente ou simultânea é usada para dois tipos de tarefas: tarefas I/O-bound ou operações de input/output e CPU-bound que indica operações que são altamente dependes da CPU.
Assim solicitar
dados de uma rede, acessar um banco de dados ou ler e gravar são tarefas
vinculadas a IO e tarefas de CPU são tarefas que são computacionalmente
caras, como cálculos matemáticos ou processamento gráfico.
As operações assíncronas são adequadas para tarefas vinculadas a I/O e a
operações paralelas são adequadas para tarefas vinculadas à CPU. Ao contrário de
outras linguagens, a classe Task pode ser usada
para operações assíncronas e paralelas.
Aqui temos duas opções :
Task
Taskt<TResult>
Task representa uma operação simultânea, enquanto
Task<TResult> representa uma operação simultânea que pode retornar um
valor e ambas são executadas de forma assíncrona.
O método Task.Run é usado para executar o código
vinculado à CPU simultaneamente; idealmente em paralelo. Ele enfileira o
trabalho especificado para execução no ThreadPool e
retorna um handle Task ou Task<TResult> para esse trabalho.
A plataforma .NET contém vários métodos, como
StreamReader.ReadLineAsync ou HttpClient.GetAsync, que executam código
vinculado a E/S de forma assíncrona. Eles são usados em conjunto com as
palavras-chave async/await.
Nota: É recomendado usar Task.Run ao invés de new Task(); task.Start().
Usando Task.Run
Este método coloca a tarefa a ser realizada na fila para execução no ThreadPool e retorna uma Task ou Task<T>.
Assim, o método Task.Run coloca uma tarefa em um thread diferente sendo adequado para tarefas vinculadas à CPU.
Como exemplo de uso de Task.Run temos o trecho de código abaixo definido em um programa Console no .NET 7.0 usando o recurso Top Level Statement:
Console.WriteLine($"Main
thread {getThreadId()}
início"); Task.Run(() => Console.WriteLine($"Thread {getThreadId()} início"); Thread.Sleep(3000); Console.WriteLine($"Thread {getThreadId()} fim"); }); Console.WriteLine( $"Main thread {getThreadId()} fim");Console.ReadLine(); int getThreadId() { return Thread.CurrentThread.ManagedThreadId; } |
Resultado:
Observe que a thread principal termina antes da tarefa gerada. Para ver a tarefa concluída, usamos o Console.ReadLine que aguarda a entrada do usuário.
A seguir temos um exemplo de Task<T> que representa uma tarefa que retorna um resultado:
Console.WriteLine("###
Usando Task<T> ###\n"); Task<int> tarefa = Task.Run(() => { Thread.Sleep(3000); return 2 + 3; }); var resultado = await tarefa;Console.WriteLine( $"2 + 3 = {resultado}");Console.ReadKey(); |
Resultado :
Este código mostra como esperar por uma tarefa que retorna um resultado do processamento.
Aqui usamos o operador await que suspende a avaliação do método assíncrono enquanto a operação assíncrona representada por seu operando não for concluída.
Quando a operação assíncrona for concluída, o operador await retornará o resultado da operação, se houver.
Usando Task.Delay
O método Task.Delay cria uma tarefa que é concluída após um atraso ou delay.
Console.WriteLine("etapa 1");
await ExecutaTarefa();
Console.WriteLine("etapa 2");
async Task ExecutaTarefa()
{
await Task.Delay(3000);
Console.WriteLine("tarefa concluída após 3s");
}
Console.ReadKey();
|
Resultado:
O método ExecutaTarefa que processa a tarefa precisa utilizar a palavra-chave async e a chamada do método precisa usar o operador await.
O método Task.Delay cria uma nova tarefa, que dorme por três segundos.
O operador await aguarda a conclusão da tarefa e bloqueia a execução do programa principal até que a tarefa seja concluída. (Ele suspende a avaliação do método async até que a operação assíncrona seja concluída)
As palavras-chave async e await são a parte central da programação assíncrona. Ao usar essas duas palavras-chave, você poderá usar recursos da plataforma .NET ou do Windows Runtime para criar um método assíncrono de forma bem simples.
Usando o método Task.WaitAll
O método Task.WaitAll espera que todos os objetos Task fornecidos concluam a execução.
using
System.Diagnostics; Console.WriteLine( "## Usando Task.WaitAll ##\n");var sw = new Stopwatch();sw.Start(); Task.WaitAll(T1Async(), T2Async(), T3Async()); sw.Stop(); var tempo = sw.ElapsedMilliseconds;Console.WriteLine($"\nTempo gasto : {tempo} ms"); async Task T1Async(){ await Task.Delay(4000); Console.WriteLine("T1Async concluído"); } async Task T2Async() { await Task.Delay(7000); Console.WriteLine("T2Async concluído"); } async Task T3Async() { await Task.Delay(2000); Console.WriteLine("T3Async concluído"); } Console.ReadKey(); |
Resultado:
Neste código estamos exeutando 3 métodos assíncronos onde usamos as palavras-chave async/await.
Dessa forma caracterizar os métodos assíncronos da seguinte forma:
A assinatura do método deve incluir o modificador async;
O método deve ter um tipo de retorno da Task<TResult>, Task ou void;
As declarações de método devem incluir pelo menos uma única expressão await - isso diz ao compilador que o método precisa ser suspenso enquanto a operação aguardada estiver ocupada.
Por último, o nome do método deve terminar com o sufixo "async" (mesmo que isso seja mais convencional do que o necessário).
Desta forma para criar código assíncrono usamos as palavras chaves async e await onde por padrão um método modificado por uma palavra-chave async contém pelo menos uma expressão await.
Usando o método Task.ContinueWith
O método Task.ContinueWith cria uma continuação que
é executada de forma assíncrona quando o Task<TResult> de destino for concluído.
Este método possui diversas sobrecargas.
Console.WriteLine("##
Usando Task.ContinueWith ##\n"); Task< int> tarefa =Task.Run(() => ExecutaTarefa()) .ContinueWith<int>((x) => x.Result * 2); var resultado = await tarefa;Console.WriteLine(resultado); int ExecutaTarefa(){ int x = 1; int y = 2; int z = 3; Thread.Sleep(1000); return x + y + z; } Console.ReadKey(); |
Resultado:
Neste código
estamos encadeando duas operações usando o método
ContinueWith de forma que o método vai somar os números 1,2 e 3 e a
seguir vai multiplar por 2.
Executando múltiplos request assíncronos
A classe HttpClient é usada para enviar solicitações HTTP e receber respostas HTTP do recurso especificado. Abaixo temos um exemplo de uso desta classe:
using
System.Diagnostics; Console.WriteLine( "## Realizando vários requests com HttpClient ##\n");var urls = new string[] { "https://macoratti.net", "http://twitter.com","https://uol.com", "http://microsoft.com", "https://github.com" }; var sw = new Stopwatch();sw.Start(); using var client = new HttpClient();var tarefas = new List<Task<HttpResponseMessage>>();foreach (var url in urls){ Console.WriteLine($"GetAsync() na url {url}"); tarefas.Add(client.GetAsync(url)); } Console.WriteLine( "\nAguardando o resultado...\n");Task.WaitAll(tarefas.ToArray()); var dados = new List<HttpResponseMessage>();foreach (var tarefa in tarefas){ dados.Add(await tarefa); } sw.Stop(); var tempo = sw.ElapsedMilliseconds; foreach (var resultado in dados){ Console.WriteLine(resultado.StatusCode); } Console.WriteLine( $"\nTempo gasto: {tempo} ms");Console.ReadKey(); |
Resultado:
Entendendo o código:
Enviamos
solicitações GET assíncronas para várias páginas da Web e obtemos seus códigos
de status de resposta.
tarefas.Add(client.GetAsync(url));
O GetAsync envia um request GET para a URL
especificada e retorna o corpo da resposta em uma operação assíncrona. Ele
retorna uma nova tarefa e a tarefa é adicionada à lista de tarefas.
Task.WaitAll(tarefas.ToArray());
O método Task.WaitAll espera que todas as tarefas
fornecidas concluam a execução.
dados.Add(await tarefa);
O operador await aguarda o resultado da operação
que é exibido no laço foreach:
foreach (var resultado em dados)
{
Console.WriteLine(resultado.StatusCode);
}
Estamos exibindo o código de status da operação , onde Ok significa que a operação foi concluida com sucesso.
Para concluir, não pense que você pode sair por ai usando async e await indiscriminadamente. Existem algumas regras básicas que devem ser seguidas :
Obs: Esteja atento e verifique se houve alguma mudança nos comportamentos acima em novas versões do C#.
E estamos conversados...
Disse Jesus: "Olhai para as aves do céu, que nem semeiam, nem segam, nem
ajuntam em celeiros; e vosso Pai celestial as alimenta. Não tendes vós muito
mais valor do que elas?"
Matheus 6:26
Referências:
NET - Unit of Work - Padrão Unidade de ...
http://msdn.microsoft.com/en-us/library/vstudio/hh156513.aspx