Search code examples
c#concurrencysystem.threading.channels

Does Channel<T> support multiple ChannelReaders and ChannelWriters, or only one of each?


The documentation for Channel.CreateUnbounded says:

Creates an unbounded channel usable by any number of readers and writers concurrently.

However Channel has properties for a single ChannelReader and ChannelWriter only, and there doesn't appear to be a way to create a reader/writer explicitly around an existing channel.

I had thought that if you had multiple producers/consumers they should share the same instance of a writer/reader, is this incorrect? Is the "number of readers/writers" talking about concurrent access rather than number of class instances?


Solution

  • Yes, the documentation means multiple producers (writers) and consumers (readers). All producers are allowed to use the single ChannelWriter of the channel, and all consumers are allowed to use its single ChannelReader. No external synchronization is required. The Channel<T> class is 100% thread-safe.

    To be clear, the above is about a Channel<T> instantiated with the default options. It is possible to create intentionally a non-thread-safe unbounded channel by configuring it with the SingleReader option:

    Channel<T> channel = Channel.CreateUnbounded<T>(new UnboundedChannelOptions()
    {
        SingleReader = true
    });
    

    With this configuration you get a channel that is presumably faster than the default, but it does not work correctly when used by multiple consumers. Technically you get a Channel<T> implementation that instead of being backed by a ConcurrentQueue<T>, it is backed by the internal collection SingleProducerSingleConsumerQueue<T>.

    There is also a SingleWriter option, which is currently (.NET 9) purely decorative. Setting this option has no effect on the Channel<T> implementation that you get (source code).

    Important: In case of multiple consumers, each item passed through the channel will be received by only one consumer. The Channel<T> doesn't support propagating one element to multiple consumers, like it does the BroadcastBlock<T> dataflow block for example.