C# - Concorrência , Paralelismo, Multithread e Assincronismo no .NET   


Hoje veremos os conceitos de concorrência, paralelismo, multithreading e assincronismo aplicados na plataforma .NET.

Os termos concorrência, paralelismo, multithreading e programação assíncrona muitas vezes são aplicados de forma intercambiáveis na plataforma .NET embora eles não sejam a mesma coisa.

Na verdade existe uma confusão na utilização dos termos e aos conceitos usados que estão relacionados com a execução assíncrona usando async e await.

Vamos apresentar cada conceito de forma objetiva e clara para tentar dirimir essa confusão.

Concorrência

Concorrência significa realizar mais de uma tarefa ao mesmo tempo.

Como exemplo temos as aplicações clientes que usam a concorrência para responder a uma entrada do usuário e gravar no banco de dados ou aplicações web que usam a concorrência para responder a um segundo request enquanto concluem o atendimento ao primeiro request.

A concorrência é necessária sempre que você precisar que uma aplicação realize uma tarefa enquanto esta trabalhando em outra. Desta forma realizar tarefas de forma concorrentes é o oposto de realizar tarefas de forma sequencial.

A concorrência não é a mesma coisa que multithreading.

Multithread

Multithread é uma forma de concorrência (mas não é a única) que usa mais de um thread, ou linhas de execução, para realizar mais de uma tarefa ao mesmo tempo.

Nota: Uma thread é uma sequência de instruções que pode ser executada independente de outro código.

Assim uma aplicação web inicia o processamento de um request em uma thread e a seguir, se outro request chegar enquanto ela esta processamento o primeiro request, ela inicia o processamento em outra thread.

Paralelismo

O paralelismo ou processamento em paralelo realiza muitas tarefas dividindo-as em várias threads(linhas de execução) que são executadas de forma concorrentes ou simultâneas.

Assim o paralelismo usa a multithreading para maximizar o uso de vários processadores. As CPUs modernas têm vários núcleos e, se houver muito trabalho a fazer, então não faz sentido apenas um núcleo fazer todo o trabalho enquanto os outros ficam ociosos. O processamento paralelo dividirá o trabalho entre várias threads, que podem ser executadas independentemente em um núcleo diferente.

O paralelismo é um tipo de multithreading que é também um tipo de concorrência.

A programação paralela deve ser usada sempre que você tiver uma boa quantidade de trabalho de computação que pode ser dividida em partes independentes de trabalho.  Ela aumenta o uso da CPU temporariamente para melhorar o rendimento; isso é desejável em sistemas cliente onde as CPUs estão frequentemente ociosas, mas geralmente não é apropriado para sistemas de servidor.

A maioria dos servidores possui algum paralelismo integrado; por exemplo, a ASP.NET tratará várias solicitações em paralelo. Escrever código paralelo no servidor ainda pode ser útil em algumas situações (se você sabe que o número de usuários simultâneos sempre será baixo), mas em geral, a programação paralela no servidor funcionaria contra o paralelismo embutido e não forneceria qualquer benefício real.

Existem duas formas de paralelismo: paralelismo de dados e paralelismo de tarefas.

1- Paralelismo de dados é quando você tem um monte de itens de dados para processar e o processamento de cada parte dos dados é independente das outras partes.

2- Paralelismo de tarefas é quando você tem um conjunto de trabalho a fazer e cada parte do trabalho é independente das outras partes. O paralelismo de tarefas pode ser dinâmico; se uma peça de trabalho resultar em várias peças de trabalho adicionais, elas podem ser adicionadas ao conjunto de trabalho.

Outro tipo de concorrência é a programação assíncrona.

Assincronismo ou Programação assíncrona

A programação assíncrona permite usar as threads em nossos processos de uma maneira mais eficiente sendo uma forma de concorrência que usa recursos como future  ou promisses e callbacks para evitar threads desnecessárias.

Um future ou promisse é um tipo que representa alguma operação que será concluída no futuro. Na plataforma .NET os tipos equivalentes usados são são Task e Task<TResult>. As APIs assíncronas mais antigas usam callbacks ou eventos em vez de future.

A programação assíncrona é centrada na ideia de que alguma operação que é iniciada e será concluída algum tempo depois. Enquanto a operação está em andamento, ela não bloqueia a thread original; a thread que inicia a operação está livre para fazer outro trabalho. Quando a operação for concluída, ele notifica seu future ou invoca seu callback de conclusão para permitir que o aplicativo saiba que a operação foi concluída.

A programação assíncrona é uma forma poderosa de concorrência, mas, até recentemente, exigia um código extremamente complexo. Na plataforma .NET temos o C# com suporte a async e await que tornaram a programação assíncrona quase tão fácil quanto a programação síncrona (não simultânea).

A programação assíncrona tem dois benefícios principais:

  1. O primeiro benefício é para programas com interface gráfica de usuário como os projetos Windows Forms ou WPF:  Neste caso a programação assíncrona permite a capacidade de resposta onde um programa assíncrono pode permanecer responsivo à entrada do usuário enquanto está funcionando;
     
  2. O segundo benefício é para programas do lado do servidor: a programação assíncrona permite escalabilidade. Um aplicativo de servidor pode escalar um pouco apenas usando o pool de threads, mas um aplicativo de servidor assíncrono geralmente pode escalar uma ordem de magnitude melhor do que isso.

Os aplicativos .NET assíncronos modernos usam as palavras-chave: async e await.

A palavra-chave async é adicionada a uma declaração de método, e seu objetivo principal é habilitar a palavra-chave await dentro desse método, e, um método assíncrono deve retornar uma Task<T> se retornar um valor, ou retornar um Task se não retornar um valor. Esses tipos de tarefas representam futures; eles notificam o código de chamada quando o método assíncrono for concluído.

Na segunda parte do artigo vamos focar na programação assíncrona usando async e await.

"17 Quando o vi (Jesus), caí aos seus pés como morto. Então ele colocou sua mão direita sobre mim e disse: "Não tenha medo. Eu sou o primeiro e o último. 18 Sou aquele que vive. Estive morto mas agora estou vivo para todo o sempre! E tenho as chaves da morte e do Hades."
Apocalipse 1:17,18

Referências:


José Carlos Macoratti