判断 Channel<T> 是否为空的最快方法是什么?

问题描述 投票:0回答:2

我在 Channel<object>

 循环中消耗 
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
方法会触发这样的爆发。

c# performance .net-6.0 channel system.threading.channels
2个回答
4
投票

我使用自制基准测试了两种方法(无界通道和有界通道)的性能。本质上,我用 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循环在
内部
实现的。所以我不应该因为复制相同的模式而失去任何东西。


3
投票

唯一真正的选择是

TryPeek
,因为它是所有三种通道风格(BoundedSingle-Reader Unboundedmulti-reader Unbounded)中唯一可用的方法。 Single-Reader 无界通道没有 Count 实现。

如果可用,

Count
委托给基础集合的计数(boundedunbounded。在有界通道中,它是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
可能会更快。

© www.soinside.com 2019 - 2024. All rights reserved.