├── .gitignore ├── ChannelsExample ├── Envelope.cs ├── ChannelsExample.csproj ├── Logger.cs ├── Producer.cs ├── Consumer.cs └── Program.cs └── ChannelsExample.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | bin/ 4 | obj/ -------------------------------------------------------------------------------- /ChannelsExample/Envelope.cs: -------------------------------------------------------------------------------- 1 | namespace ChannelsExample 2 | { 3 | public class Envelope 4 | { 5 | public Envelope(string payload) 6 | { 7 | Payload = payload; 8 | } 9 | 10 | public string Payload { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /ChannelsExample/ChannelsExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ChannelsExample/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ChannelsExample 4 | { 5 | public class Logger 6 | { 7 | public static void Log(string text, ConsoleColor color = ConsoleColor.White) 8 | { 9 | // since this method might be called by different threads, 10 | // we need to use a lock to guarantee we set the right color 11 | lock (Console.Out) 12 | { 13 | Console.ForegroundColor = color; 14 | Console.WriteLine($"[{DateTime.UtcNow:hh:mm:ss.ff}] - {text}"); 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ChannelsExample/Producer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Channels; 4 | using System.Threading.Tasks; 5 | 6 | namespace ChannelsExample 7 | { 8 | public class Producer 9 | { 10 | private readonly ChannelWriter _writer; 11 | private readonly int _instanceId; 12 | 13 | public Producer(ChannelWriter writer, int instanceId) 14 | { 15 | _writer = writer; 16 | _instanceId = instanceId; 17 | } 18 | 19 | public async Task PublishAsync(Envelope message, CancellationToken cancellationToken = default) 20 | { 21 | await _writer.WriteAsync(message, cancellationToken); 22 | Logger.Log($"Producer {_instanceId} > published '{message.Payload}'", ConsoleColor.Yellow); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ChannelsExample.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChannelsExample", "ChannelsExample\ChannelsExample.csproj", "{1BBE508F-941C-4AA2-BAF6-6CE70D147E9F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1BBE508F-941C-4AA2-BAF6-6CE70D147E9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1BBE508F-941C-4AA2-BAF6-6CE70D147E9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1BBE508F-941C-4AA2-BAF6-6CE70D147E9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1BBE508F-941C-4AA2-BAF6-6CE70D147E9F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F8CD6996-7F6F-4AAD-8A32-403A832F1FE8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ChannelsExample/Consumer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Channels; 4 | using System.Threading.Tasks; 5 | 6 | namespace ChannelsExample 7 | { 8 | public class Consumer 9 | { 10 | private readonly ChannelReader _reader; 11 | private readonly int _instanceId; 12 | private static readonly Random Random = new Random(); 13 | 14 | public Consumer(ChannelReader reader, int instanceId) 15 | { 16 | _reader = reader; 17 | _instanceId = instanceId; 18 | } 19 | 20 | public async Task BeginConsumeAsync(CancellationToken cancellationToken = default) 21 | { 22 | Logger.Log($"Consumer {_instanceId} > starting", ConsoleColor.Green); 23 | 24 | try 25 | { 26 | await foreach (var message in _reader.ReadAllAsync(cancellationToken)) 27 | { 28 | Logger.Log($"CONSUMER ({_instanceId})> Received: {message.Payload}", ConsoleColor.Green); 29 | await Task.Delay(500, cancellationToken); 30 | } 31 | } 32 | catch (OperationCanceledException ex) 33 | { 34 | Logger.Log($"Consumer {_instanceId} > forced stop", ConsoleColor.Green); 35 | } 36 | 37 | Logger.Log($"Consumer {_instanceId} > shutting down", ConsoleColor.Green); 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /ChannelsExample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Channels; 6 | using System.Threading.Tasks; 7 | 8 | namespace ChannelsExample 9 | { 10 | class Program 11 | { 12 | static async Task Main(string[] args) 13 | { 14 | await Run(1, 10, 1, 1); 15 | 16 | await Run(1, 10, 1, 3); 17 | 18 | await Run(100, 10, 1, 3); 19 | 20 | Logger.Log("done!"); 21 | Console.ReadLine(); 22 | } 23 | 24 | private static async Task Run(int maxMessagesToBuffer, int messagesToSend, int producersCount, int consumersCount) 25 | { 26 | Logger.Log("*** STARTING EXECUTION ***"); 27 | Logger.Log($"producers #: {producersCount}, buffer size: {maxMessagesToBuffer}, consumers #: {consumersCount}"); 28 | 29 | var channel = Channel.CreateBounded(maxMessagesToBuffer); 30 | 31 | var tokenSource = new CancellationTokenSource(); 32 | var cancellationToken = tokenSource.Token; 33 | 34 | var tasks = new List(StartConsumers(channel, consumersCount, cancellationToken)) 35 | { 36 | ProduceAsync(channel, messagesToSend, producersCount, tokenSource) 37 | }; 38 | await Task.WhenAll(tasks); 39 | Logger.Log("*** EXECUTION COMPLETE ***"); 40 | } 41 | 42 | private static async Task SingleProducerMultiConsumers(int maxMessagesToBuffer, int messagesToSend, int producersCount, int consumersCount) 43 | { 44 | var channel = Channel.CreateBounded(maxMessagesToBuffer); 45 | 46 | var tokenSource = new CancellationTokenSource(); 47 | var cancellationToken = tokenSource.Token; 48 | 49 | var tasks = new List(StartConsumers(channel, consumersCount, cancellationToken)) 50 | { 51 | ProduceAsync(channel, messagesToSend, producersCount, tokenSource) 52 | }; 53 | await Task.WhenAll(tasks); 54 | } 55 | 56 | private static Task[] StartConsumers(Channel channel, int consumersCount, CancellationToken cancellationToken) 57 | { 58 | var consumerTasks = Enumerable.Range(1, consumersCount) 59 | .Select(i => new Consumer(channel.Reader, i).BeginConsumeAsync(cancellationToken)) 60 | .ToArray(); 61 | return consumerTasks; 62 | } 63 | 64 | private static async Task ProduceAsync(Channel channel, 65 | int messagesCount, 66 | int producersCount, 67 | CancellationTokenSource tokenSource) 68 | { 69 | var producers = Enumerable.Range(1, producersCount) 70 | .Select(i => new Producer(channel.Writer, i)) 71 | .ToArray(); 72 | 73 | int index = 0; 74 | 75 | var tasks = Enumerable.Range(1, messagesCount) 76 | .Select(i => 77 | { 78 | index = ++index % producersCount; 79 | var producer = producers[index]; 80 | var msg = new Envelope($"message {i}"); 81 | return producer.PublishAsync(msg, tokenSource.Token); 82 | }) 83 | .ToArray(); 84 | await Task.WhenAll(tasks); 85 | 86 | Logger.Log("done publishing, closing writer"); 87 | channel.Writer.Complete(); 88 | 89 | Logger.Log("waiting for consumer to complete..."); 90 | await channel.Reader.Completion; 91 | 92 | Logger.Log("Consumers done processing, shutting down..."); 93 | tokenSource.Cancel(); 94 | } 95 | } 96 | } 97 | --------------------------------------------------------------------------------