Programação assíncrona com listas em .Net

Nós, desenvolvedores, constantemente nos deparamos com sistemas que utilizam recursos com Multi-Threads. Mas, o que de fato é Threads e programação assíncrona? 🤔

Bom, programação assíncrona é quando você quer executar algo, mas não precisa esperar/travar seu sistema aguardando o retorno desta execução.

E Thread? Imagine que uma thread seria um pequeno programa, que executa alguma determinada tarefa. Em um sistema pode existir uma ou mais threads e o objetivo de se ter mais threads seria a execução de determinadas tarefas de maneira mais rápida, seria o famoso ditado “dividir para conquistar”.

E o que ambos tem haver um com o outro? Tudo! Quando você utiliza Threads no seu sistema, como recurso para algo, você está aplicando, de certa forma, programação assíncrona. Você cria um sub-processo, “pede” para ele executar algo (separadamente) e, enquanto isso, seu sistema continua realizando outras funções.

E, também, é importante saber um importante termo, que seria o significado de Thread-Safe! Thread-Safe é um conceito utilizado para programação multi-threaded. Uma ou mais threads podem acessar um mesmo recurso ao mesmo tempo, o que não é bom. Thread-Safe é uma técnica que manipula a estrutura de dados compartilhada de tal forma que garante a execução segura desse trecho de código por várias threads, evitando a tentativa de gravar algo em um mesmo local e ao mesmo tempo.

Legal né? Mas temos alguns pontos de atenção 🚨 e curiosidades 🤓:

  • Você, que é desenvolvedor, nunca crie mais threads do que a quantidade de discos lógicos que sua máquina possui, pois isso pode tornar o processamento mais lento, o que é o oposto da intenção do uso de threads.
  • Threads podem se comunicar entre si
  • Cuidado ao utiliza algum trecho de código que não seja Thread-Safe, pois pode haver algum conflito nessa parte.
  • O .NET, fornece alguns recursos para encapsular o uso “raiz” de Threads! São as Task Parallel Library (TPL)!
  • 🆘 Nem sempre o parelelismo será a solução! É a famosa frase “depende de cada contexto”, ou seja, depende da quantidade de processamento que será feito, o que será feito, por exemplo.

Agora vamos ao que interessa, a parte prática! 💻

Temos duas opções fáceis, em .NET, para realizar essas operações! São os recursos Parallel.For e Parallel.Foreach. Ambos executa operações com dados locais de uma lista de forma paralela. Estas duas funções fornecem algumas configurações o qual facilita a manipulação de acordo com a necessidade.

Vejamos alguns exemplos:

Agora vamos analisar alguns pontos:

  • Observe que em ambas as situações eu posso controlar a quantidade de paralelismo que irá ser feita, através da classe ParallelOptions.
  • Em ambas as situações eu consigo controlar variáveis Thread-Safe, através do argumento Action<TSource, ParallelLoopState, long> body, para iterar a lista.

E esse é o ponto crucial deste artigo! E se, por algum motivo, você for iterar um lista, processar algo e, com o resultado, alimentar uma nova lista? O primeiro passo é pensar no termo “Thread-Safe”, mas não precisamos fazer com que uma lista fique Thread-Safe. Em C# temos alguns tipos de listas que já são Thread-Safe. Vejamos alguns tipos:

  • BlockingCollection: coleção thread-safe
  • ConcurrentDictionary: implementação thread-safe de um dicionário
  • ConcurrentQueue: implementação thread-safe de filas FIFO
  • ConcurrentStack: implementação thread-safe de filas LIFO
  • ConcurrentBag: implementação thread-safe de coleção não ordenada
  • IProducerConsumerCollection: interface que deve ser implementada para o uso de um BlockingCollection

Vejamos, como exemplo, como utilizar o BlockingCollection:

E por quê devemos utilizar algum desses tipos de lista?

  • Primeiro para não precisarmos transformar algum tipo de lista, mais comum, em Thread-Safe.
  • Segundo para não haver conflitos. Pois, por exemplo, se for utilizar um List, pode haver tentativa de acessar um mesmo índice por mais de uma Thread ao mesmo tempo e, assim, disparar uma exceção em seu sistema.

Threads é um recurso importante e poderoso! É fundamental que todo desenvolvedor tenha algum conhecimento básico sobre isso para, pelo menos, tentar compreender como funcionam. Sabendo do contexto e das possibilidades para empregar neste contexto, é possível otimizar muito o processamento. Além disso, podemos perceber que, hoje, não há tantas necessidades em criar elementos Thread-Safe do zero, por já haver recursos já existentes, como as listas mostradas acima.

Espero que tenham gostado! Se sim, deixe seu clap 👏 e algum comentário sobre o que achou ou o que gostaria de ver mais sobre este assunto! 😁

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.