I am consuming a Channel<object> in an await foreach loop, and on each iteration I want to know if the channel is empty. Channels don't have an IsEmpty property, so I am seeing two ways to get this information: the Count property and the TryPeek method:
await foreach (var item in channel.Reader.ReadAllAsync())
{
ProcessItem(item);
if (channel.Reader.Count == 0) DoSomething();
}
await foreach (var item in channel.Reader.ReadAllAsync())
{
ProcessItem(item);
if (!channel.Reader.TryPeek(out _)) DoSomething();
}
My question is: Which is the most performant way to get the IsEmpty information? Is it one of the above, or is it something else?
I should mention that my channel is unbounded, but I am open to switching to a bounded channel (Channel.CreateBounded<object>(Int32.MaxValue)) in case this would be beneficial performance-wise.
My unbounded channel is configured with the SingleReader option equal to false (the default value).
Another detail that might be important: most of the time the check for emptiness will be negative. The producer of the channel tends to write hundreds of items in frequent bursts. The DoSomething method triggers such a burst.
I measured the performance of both approaches, with both unbounded and bounded channels, using a home-made benchmark. Essentially I filled a channel with 1,000 elements, and retrieved the
IsEmptyinformation 20,000,000 times in a loop. Here are the results:Online demo.
So it seems that for my unbounded channel the
.TryPeek(out _)is the faster approach. There is no need to switch to a bounded channel. The performance is pretty good regardless though.It should be noted that in my particular use case, it is possible to obtain the
IsEmptyinformation essentially for free, by switching from theawait foreachloop to a nestedwhileloop like this:Each time the inner
whileloop completes, the channel is temporarily empty.The
ChannelReader<T>.ReadAllAsyncmethod is implemented internally with a similar nestedwhileloop. So I shouldn't lose anything by replicating the same pattern.