.NET Core - Apresentando a classe Channel<T>


 Neste artigo vou apresentar a classe Channel<T> que foi introduzida com o .NET Core 3.X.

Na documentação oficial a definição da classe Channel<T>, que pertence ao namespace System.Threading.Channels, diz que a classe Channel<T> fornece uma classe raiz para canais que dão suporte à leitura e gravação de elementos do tipo T.

Nem preciso dizer que esta definição não ajuda a entender o que é e nem para que serve um channel.

Então vamos tentar destrinchar esse conceito...

Em sua essência, um Channel ou Canal é essencialmente um novo tipo de coleção na plataforma .NET que atua de forma muito semelhante ao tipo Queue<T> existente mas com benefícios adicionais.

Assim um channel ou canal pode ser entendido como uma estrutura de dados básica para estabelecer uma conexão entre produtores e consumidores.

Podemos pensar em um canal como sendo um conceito de sincronização que suporta a passagem de dados entre produtores e consumidores, normalmente simultaneamente. Um ou mais produtores podem gravar dados no canal, que são então lidos por um ou mais consumidores.

Logicamente, um canal é efetivamente uma fila thread-safe eficiente.

Dessa forma este namespace contém tipos que podemos usar para implementar um cenário de comunicação produtor-consumidor permitindo que os atores realizem tarefas simultâneas, e, usando tipos de sincronização, possam trocar dados assíncronos entre si.

Temos assim os típicos atores que atuam em uma filaprodutores ou publishers e consumidores ou subscribers, onde, existe alguém publicando uma mensagem e um ou vários 'assinantes' ouvindo essa mensagem e agindo de acordo a ela. Não há distribuição de carga porque, conforme você adiciona assinantes, eles basicamente obtêm uma cópia das mesmas mensagens que todos os outros.

Para tentar facilitar o entendimento temos a seguir um diagrama simplificando descrevendo o processo:

E ainda para ilustrar o processo produtos/consumidor para mostrar como usar channel e em como este recurso pode ser útil é pensar em na seguinte situação:

  1. Os clientes em um mercado/shopping estão se dirigindo aos caixas;

  2. A medida que a quantidade de clientes aumente as filas nos caixas ficam mais longas;

  3. Para processar os clientes basta abrir mais caixas de atendimento;

  4. Se não forem abertos mais caixas para atendimento as filas vão crescer;

  5. Se você abrir caixas para atendimento em excesso , não haverá clientes, e eles ficaram ociosos;

Geralmente, isso é chamado de problema produtor-consumidor e que o Channels visa corrigir.

Channel : Exemplo básico

Agora vejamos um exemplo extremamente básico de código usando Channel.

Os recursos necessários que precisamos usar estão contidos no namespace System.Threading.Channels onde temos os métodos para criar, usar e fechar um canal.

Vamos criar um projeto Console no ambiente do .NET 5.0 e criar um canal bem simples:

Obs: Aqui estou usando o recurso Top Level Statement do C# 9.

Entendendo o código:

1- Criamos um canal usando o método CreateUnbounded<T>() que cria um canal sem capacidade definida que esta limitado a memória RAM disponível.  Criar canais dessa forma pode ser perigoso uma vez que eles não tem limitação e podem consumir toda a memória disponível.

2- A seguir estamos escrevendo 10 itens do tipo int no canal usando o método WriteAsync();

3- A seguir estamos percorrendo o canal e lendo e exibindo os itens escritos usando o método ReadAsync();

Cabe destacar que os Channels são Thread-Safe o que permite que vários threads podem estar lendo/gravando no mesmo canal sem problemas.

A seguir temos os principais métodos presentes neste namespace:

Essas classes herdam da classe ChannelOptions que fornece opções para controlar o comportamento do canal e que possui as propriedades:

  1. SingleWriter:  Quando definida como true garante que vai ocorrer apenas uma operação de escrita por vez.(o valor padrão é false)
  2. SingleReader: Atua da mesma forma que a anterior para operações de leitura no canal;

Agora com o uso do recurso Channels você pode separar os produtores dos consumidores em um cenário de publicação e assinatura, onde os produtores e consumidores não apenas melhoram o desempenho trabalhando em paralelo, mas também é possível criar mais produtores ou consumidores caso uma dessas tarefas comece a superar a outra aumentando assim o rendimento da sua aplicação.

Em outro artigo vamos ver como podemos usar este recurso de uma forma mais útil.

"Quão grandes são, Senhor, as tuas obras! Mui profundos são os teus pensamentos."
Salmos 92:5

Referências:


José Carlos Macoratti