循环中消耗
await foreach
,并且在每次迭代时我想知道通道是否为空。通道没有 IsEmpty
属性,因此我看到有两种获取此信息的方法: Count
属性和 TryPeek
方法:
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();
}
我的问题是: 哪种方式是获取
IsEmpty
信息的最有效方法?是以上其中之一,还是其他?
Channel.CreateBounded<object>(Int32.MaxValue)
),以防这对性能有利。
SingleReader
选项等于 false
(默认值)。
另一个可能很重要的细节:大多数情况下,空性检查将为负数。该频道的制作人往往会频繁地突发编写数百个项目。
DoSomething
方法会触发这样的爆发。
我使用自制基准测试了两种方法(无界通道和有界通道)的性能。本质上,我用 1,000 个元素填充了一个通道,并在循环中检索
IsEmpty
信息 20,000,000 次。结果如下:
Platform: .NET 6.0.0-rtm.21522.10
Unbounded.Reader.Count: 1,000
Bounded.Reader.Count: 1,000
LoopsCount: 20,000,000
Unbounded-Count Duration: 706 msec
Unbounded-TryPeek Duration: 360 msec
Bounded-Count Duration: 470 msec
Bounded-TryPeek Duration: 506 msec
在线演示.
因此,对于我的无界通道来说,
.TryPeek(out _)
似乎是更快的方法。无需切换到有界通道。不管怎样,性能还是不错的。
应该指出的是,在我的特定用例中,通过从
IsEmpty
循环切换到嵌套 await foreach
循环,基本上可以免费获取 while
信息,如下所示:
while (await channel.Reader.WaitToReadAsync())
{
while (channel.Reader.TryRead(out var item))
{
ProcessItem(item);
}
DoSomething();
}
每次内部
while
循环完成时,通道都会暂时为空。
ChannelReader<T>.ReadAllAsync
方法是通过类似的嵌套while
循环在内部实现的。所以我不应该因为复制相同的模式而失去任何东西。
唯一真正的选择是
TryPeek
,因为它是所有三种通道风格(Bounded、Single-Reader Unbounded和multi-reader Unbounded)中唯一可用的方法。 Single-Reader 无界通道没有 Count 实现。
如果可用,
Count
委托给基础集合的计数(bounded、unbounded。在有界通道中,它是Dequeue,而在无界通道中,它是ConcurrentQueue。
ConcurrentQueue.Count非常昂贵,必须动态计算可用项目,而便宜的IsEmpty方法实际上调用
TryPeek
。
public bool IsEmpty =>
// IsEmpty == !TryPeek. We use a "resultUsed:false" peek in order to avoid marking
// segments as preserved for observation, making IsEmpty a cheaper way than either
// TryPeek(out T) or Count == 0 to check whether any elements are in the queue.
!TryPeek(out _, resultUsed: false);
对于有界通道,Count返回存储在字段中的出队计数。尽管与 ConcurrentQueue 的 Count 相比,额外的锁会增加一些开销。锁定延迟将取决于实际使用情况。多个读取器将导致更多锁定。
public override int Count
{
get
{
BoundedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
return parent._items.Count;
}
}
}
总而言之,
TryPeek
是所有通道中唯一可用的选项,也是无界通道中最快的选项。在有界通道中,根据实际使用情况,Count
可能会更快。