.NET

4 jul, 2024

Paralelismo e concorrência: exemplos práticos em .NET com C#

Publicidade

Veja nesse artigo o que seria paralelismo e concorrência através de exemplos práticos em .NET com C#

Na computação, à medida que aumentaram as demandas por eficiência e velocidade, surgiram diferentes abordagens para maximizar o uso dos recursos de hardware e lidar com múltiplas tarefas ao mesmo tempo.

Duas dessas abordagens são o Paralelismo e a Concorrência.

Neste post rápido demonstrarei de forma prática o que seria cada um deles.

Vamos la 🙂

Começando pelo Paralelismo, ele se refere à execução simultânea de múltiplas tarefas, geralmente em diferentes núcleos de um processador. Isso permite que tarefas sejam executadas ao mesmo tempo, aproveitando ao máximo o poder de processamento disponível.

Para ficar mais claro, imagine o seguinte cenário: você está trabalhando em um projeto que recebe uma lista de e-mails e deseja enviá-los para uma fila (um broker qualquer).

string[] emails = ["email1@fiap.com.br", "email2@fiap.com.br", "email3@fiap.com.br"];

foreach (var email in emails)
{
    SendEmailToQueue(email);
}

Console.WriteLine("Todos os e-mails foram enviados para a fila!");
Console.ReadLine();


static void SendEmailToQueue(string email)
{
    Console.WriteLine($"Enviando {email} para a fila... {Environment.CurrentManagedThreadId}");
    Thread.Sleep(500); 
    Console.WriteLine($"{email} foi enviado para a fila!");
}

Resultado:

Exemplo com for em C#

Note que este serviço foi executado em 1551ms e rodou somente na main thread 1.

Para que possamos paralelizar este fluxo e fazer com que os e-mails cheguem mais rapido na fila, nos podemos utilizar a classe classe Parallel do namespace System.Threading.Tasks.

Somente um ponto de atênção aqui pessoal, esse tipo de processo não é exclusivo do C#, você pode trabalhar com paralelismo com outras linguagens de programação também.

Bom, vamos atualizar o nosso exemplo anterior para que possamos ver como trabalhar com paralelismo:

using System.Diagnostics;

var stopWatch = new Stopwatch();
stopWatch.Start();
string[] emails = ["email1@fiap.com.br", "email2@fiap.com.br", "email3@fiap.com.br"];

Parallel.ForEach(emails, SendEmailToQueue);

stopWatch.Stop();

Console.WriteLine($"Todos os e-mails foram enviados para a fila! {stopWatch.ElapsedMilliseconds}ms ");
Console.ReadLine();


void SendEmailToQueue(string email)
{
    Console.WriteLine($"Enviando {email} para a fila... {Environment.CurrentManagedThreadId}");
    Thread.Sleep(500);
    Console.WriteLine($"{email} foi enviado para a fila!");
}

Neste exemplo nós atualizamos o foreach para o Parallel.foreach. (Bem conhecido pela comunidade de desenvolvedores .NET).

Resultado:

Programação paralela com .NET C#

Note que tivemos um ganho significativo no processamento do nosso sistema, o que havia sido processado em 1551ms caiu para 543ms.

Isso foi possivel porque nós utilizamos oParallel.ForEach, que divide o trabalho entre múltiplas threads, permitindo que as operações sejam executadas em paralelo.

Mas antes de avancarmos para o próximo tópico deste post, você sabe o que seria uma thread?

Caso a sua resposta seja não, uma thread é a menor unidade de processamento que um sistema operacional pode agendar.

Em termos simples, uma thread é uma sequência de instruções que pode ser executada independentemente.

Cada thread dentro de um processo compartilha o mesmo espaço de memória, o que permite que elas se comuniquem e compartilhem dados facilmente, mas também requer uma sincronização cuidadosa para evitar conflitos e inconsistências.

Agora avançando para o nosso próximo tópico sobre concorrência, ela se refere à capacidade de uma aplicação lidar com múltiplas tarefas, alternando entre elas para maximizar a eficiência.

Isso não significa que as tarefas estão sendo executadas ao mesmo tempo, mas sim que a aplicação gerencia várias tarefas de maneira que pareça simultânea.

Para que possamos entender como funciona concorrência na prática, imagine que você é um desenvolvedor trabalhando em um aplicativo de e-commerce. Você recebeu uma demanda para implementar uma funcionalidade que processa pedidos de clientes e, ao mesmo tempo, verifica se há atualizações de estoque para os produtos.

A seguir você tem um exemplo em C# que simula esse cenário usando concorrência:


using System.Diagnostics; var stopWatch = new Stopwatch(); stopWatch.Start(); Task processOrders = ProcessOrdersAsync(); Task checkInventoryUpdates = CheckInventoryUpdatesAsync(); await Task.WhenAll(processOrders, checkInventoryUpdates); stopWatch.Stop(); Console.WriteLine($"Processamento de pedidos e verificação de estoque concluídos. {stopWatch.ElapsedMilliseconds}ms"); async Task ProcessOrdersAsync() {     Console.WriteLine($"Iniciando processamento de pedidos... {Environment.CurrentManagedThreadId}");     await Task.Delay(5000);     Console.WriteLine($"Processamento de pedidos concluído. {Environment.CurrentManagedThreadId}"); } async Task CheckInventoryUpdatesAsync() {     Console.WriteLine($"Verificando atualizações de estoque... {Environment.CurrentManagedThreadId}");     await Task.Delay(3000);     Console.WriteLine($"Atualizações de estoque verificadas. {Environment.CurrentManagedThreadId}"); }

Neste exemplo, ProcessOrdersAsync simula o processamento de pedidos dos clientes, enquanto CheckInventoryUpdatesAsync simula a verificação de atualizações no estoque.

Ambas as tarefas são iniciadas quase simultaneamente, mas são executadas de forma concorrente, o que significa que o sistema operacional e o runtime do .NET gerenciam a alternância entre as tarefas para maximizar a eficiência.

Resultado:

Notem que o sistema iniciou na thread principal e depois finalizou em outra thread. Este comportamento é devido ao uso do pool de threads pelo runtime do .NET para executar operações assíncronas.

Bom, espero que tenham entendido como Paralelismo e Concorrencia funcionam e que tenham gostado deste post.

Até mais pessoal.