Skip to content

[API Proposal]: BroadcastChannelWriter #100443

Open
@stephentoub

Description

@stephentoub

I've had a few discussions lately with folks that have wanted something akin to a broadcasting ChannelWriter, where you could supply multiple channel writers and multiplex across them, with a single writer writing the same value to all of them. There's also been a desire for participants to come and go, such that targets could be added/removed dynamically. Any targets there when the write is issued would receive it, just forwarding along the write.

Effectively, it would look like this:

public sealed class BroadcastChannelWriter<T> : ChannelWriter<T>
{
    public void Add(ChannelWriter<T> target);
    public void Remove(ChannelWriter<T> target);
    public override bool TryWrite(T item);
    public override ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken = default);
    public override ValueTask WriteAsync(T item, CancellationToken cancellationToken = default);
    public override bool TryComplete(Exception? error = null);
}

Unfortunately, I don't know how to define the semantics for this in a way that makes sense for an arbitrary consumer of the type via the abstraction. WriteAsync is straightforward, e.g. the equivalent of:

public override async ValueTask WriteAsync(T item, CancellationToken cancellationToken = default)
{
    foreach (ChannelWriter<T> writer in _participants)
    {
        await writer.WriteAsync(item, cancellationToken);
    }
}

But... what, for example, should the semantics of TryWrite be? If TryWrite returns true for one participant but then false for another, what should the method return? If it returns true, that indicates to the consumer that the data was written, but it wasn't to all targets. If it returns false, that indicates to the consumer that they might try again, in which case they could end up writing the same value multiple times to some of the participants.

I'm opening this issue in case folks who are interested have suggestions for how to rationalize this, since at present I'm not comfortable adding something like this. The best I've come up with is to have the type not actually be a ChannelWriter, e.g.

public sealed class BroadcastChannelWriter<T>
{
    public void Add(ChannelWriter<T> target);
    public void Remove(ChannelWriter<T> target);
    public ValueTask WriteAsync(T item, CancellationToken cancellationToken = default);
    public override bool TryComplete(Exception? error = null);
}

such that you can't use it polymorphically and use the problematic operations. But that then also loses meaningful functionality.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions