├── .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 |
--------------------------------------------------------------------------------