février 16, 2022

Découvert des channels Asp .Net Core

Tout d’abord que sont les channels en ASP.NET Core ?

un channel est une structure ou collection de données .NET où nous pouvons stocker les données du producteur et en même temps le consommateur peut les récupérer sans avoir besoin d’une synchronisation supplémentaire de notre côté.

Celà peut se modéliser de la manière suivante :

Producer/Consumer décrit l’acte d’un producteur publiant un message, et il y a un ou plusieurs consommateurs qui peuvent agir sur ce message, mais chaque message n’est lu qu’une seule fois. Il n’est pas dupliqué à chaque abonné.

Tout celà ressemble bien sur à ce que peuvent nous fournir des messages brokers comme RabbitMq ou Kafka.

Un exemple concret !

Nous allons commencer par créer un nouveau projet WebApi vide.

Ensuite nous allons créer un producer en tant que WriterService comme suit :

WriterService :
using System.Threading.Channels;

namespace Channels.Sample
{
    public class WriterService : BackgroundService
    {
        private readonly ChannelWriter<int> _channelWriter;
        public WriterService(ChannelWriter<int> channelWriter)
        {
            _channelWriter = channelWriter;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            int count = 0;
            while(!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(1000, stoppingToken);
                Console.WriteLine($"Writing data {count}");
                await _channelWriter.WriteAsync(count++, stoppingToken);
            }
        }
    }
}

Nous venons de créer une classe hériter de BackGroundService qui va publier des messages avec seulement un entier qui s’auto incrémente et qui le publie dans le canal.

Ici nous n’avons qu’un entier mais nous pourrions imaginer des structures plus complexes.

Pour continuer un ReaderService en tant que consumer :

ReaderService :
using System.Threading.Channels;

namespace Channels.Sample
{
    public class ReaderService : BackgroundService
    {
        private readonly ChannelReader<int> _channelReader;
        public ReaderService(ChannelReader<int> channelReader)
        {
            _channelReader = channelReader;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while(!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(1000, stoppingToken);

                try
                {
                    var datasToRead = await _channelReader.ReadAsync(stoppingToken);
                    Console.WriteLine($"Reading Data : {datasToRead}");
                }
                catch (ChannelClosedException ex)
                {

                    Console.WriteLine(ex.Message);
                }

            }
        }
    }
}

Et pour finir, nous allons paramétrer tout celà dans notre program.cs en rajoutant les lignes suivantes :

builder.Services.AddSingleton(Channel.CreateUnbounded<int>(new UnboundedChannelOptions() { SingleReader = true }));
builder.Services.AddSingleton(svc => svc.GetRequiredService<Channel<int>>().Reader);
builder.Services.AddSingleton(svc => svc.GetRequiredService<Channel<int>>().Writer);
builder.Services.AddHostedService<ReaderService>();
builder.Services.AddHostedService<WriterService>();

Lorsque nous lançons l’application, voici ce que nous avons dans la fenêtre de sortie :

Bounded channels and Unbounded channels

La méthode CreateUnbounded crée un canal sans limite quant au nombre d’éléments pouvant être stockés. bien sûr, à un moment donné, il pourrait atteindre les limites de la mémoire et vous aurez une exception de mémoire.

D’autre part, la méthode CreateBounded crée un canal avec une limite explicite fournie lors de la création.

Les Bounded channels ont des options pour indiquer différents comportements :

public enum BoundedChannelFullMode
{
    Wait,
    DropNewest,
    DropOldest,
    DropWrite
}


La valeur par défaut est Wait, il attend jusqu’à ce qu’il y ait de l’espace dans la file d’attente pour l’écriture. donc TryWrite renverra false pour ce canal.

DropOldest supprimera l’élément « le plus ancien ».

DropNewest supprimera l’élément le plus récent.

DropWrite supprime l’élément en cours d’écriture.

Choisissez donc ce qui convient à votre scénario.

Nous avons vu aujourd’hui comment utiliser les « Channels » ASP .NET Core à la manière de RabbitMQ.

Vous pouvez retrouver le code produit durant le tutoriel ici : https://github.com/AlexCastroAlex/Channels.Sample

Je vous invite donc à expérimenter celà si vous n’avez besoin de celà que dans une application qui ne nécessite pas une architecture distribuée qui aurait besoin de RabbitMQ par exemple.