is it ok to cache iasyncenumerable for multiple consumers?

Let’s change slightly the implementation of the “event source” of your first example, the GetNumbersAsync method:

private static int _current = 0;
private static async IAsyncEnumerable<int> GetNumbersAsync(int maxNumber)
{
    // This would really be async read operations from a remote source
    for (var i = 0; i < maxNumber; i++)
    {
        await Task.Delay(100);
        yield return Interlocked.Increment(ref _current);
    }
}

Here is the output after this change:

1: Processing 1
2: Processing 2
2: Processing 4
1: Processing 3
2: Processing 5
1: Processing 6
1: Processing 8
2: Processing 7
2: Processing 9
1: Processing 10
1: Processing 12
2: Processing 11
1: Processing 14
2: Processing 13
1: Processing 15
2: Processing 16
1: Processing 17
2: Processing 18
1: Processing 19
2: Processing 20

Each consumer is receiving different “events”!

Although the IAsyncEnumerable in your example is a single cached instance, every time you try to enumerate it with an await foreach statement a new IAsyncEnumerator is created, with its life bounded with this specific enumeration. The IAsyncEnumerators are neither thread-safe nor reusable, and if you try to cache one and share it between consumers, with each consumer calling its MoveNextAsync method without synchronization, you’ll get undefined behavior.

If you want a source of IAsyncEnumerables that can be safely subscribed/unsubscribed at any time, and propagate all messages to subscribers that may consume them at different paces, it’s nowhere near as trivial as caching an IAsyncEnumerable created by a C# iterator (a method containing yield statements). You can find implementations of an AsyncEnumerableSource here.

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top