2

我一直在研究一个在频道中排队耗时的工作的功能,在那里我使用例如迭代频道 await foreach(var item in channel.Reader.ReadAllAsync(cancellationToken)) {...}

我期待当通过 that 请求取消时cancellationTokenReadAllAsync会在取消之后的第一次迭代中抛出。

在我看来,情况并非如此。循环一直持续到处理完所有项目,然后抛出一个OperationCanceledException.

这看起来有点奇怪,至少可以这么说。从ChannelReadergithub repo可以看到取消令牌标记有[EnumeratorCancellation]属性,因此应该将其传递给周围生成的状态机yield return item;(如果我错了,请纠正我)。

我的问题是,这是(有点)正常的行为ReadAllAsync(CancellationToken),还是我错过了什么?

这是一个演示问题的简单测试代码(在 dotnetfiddle 上尝试):

var channel = Channel.CreateUnbounded<int>();
for (int i = 1; i <= 10; i++) channel.Writer.TryWrite(i);
int itemsRead = 0;
var cts = new CancellationTokenSource();
try
{
    await foreach (var i in channel.Reader.ReadAllAsync(cts.Token))
    {
        Console.WriteLine($"Read item: {i}. Requested cancellation: " +
            $"{cts.Token.IsCancellationRequested}");

        if (++itemsRead > 4 && !cts.IsCancellationRequested)
        {
            Console.WriteLine("Cancelling...");
            cts.Cancel();
        }
    }
}
catch (OperationCanceledException)
{
    Console.WriteLine($"Operation cancelled. Items read: {itemsRead}");
}

这是上面的输出。请注意在中间取消项目后如何继续获取项目:

Read item: 1. Requested cancellation: False
Read item: 2. Requested cancellation: False
Read item: 3. Requested cancellation: False
Read item: 4. Requested cancellation: False
Read item: 5. Requested cancellation: False
Cancelling...
Read item: 6. Requested cancellation: True
Read item: 7. Requested cancellation: True
Read item: 8. Requested cancellation: True
Read item: 9. Requested cancellation: True
Read item: 10. Requested cancellation: True
Operation cancelled. Items read: 10
4

2 回答 2

1

此行为是设计使然。我正在复制粘贴Stephen Toub对相关GitHub 问题的回复:

我想问一下这种行为是否是设计使然。

这是。已经有数据可供立即阅读,因此实际上没有什么可以取消的。迭代器的实现就在这个紧密的循环中:

while (TryRead(out T? item)) 
{ 
   yield return item; 
} 

只要数据立即可用。一旦没有,它就会逃到外循环,它会检查取消。

也就是说,它可以改变。我对取消是否更可取没有强烈的意见。我希望这将取决于用例。

于 2021-08-04T16:50:41.660 回答
1

对于必须研究相同问题的人来说,更多的是更新而不是实际答案:此行为现在在有关ChannelReader.ReadAllAsync() 此处的文档中指定

由于提出了这个主题,添加了描述取消令牌效果的第二句话(“如果数据立即可供读取,那么即使在请求取消之后也可能产生该数据。 ”)已添加。

感谢所有参与的人!

于 2021-08-12T13:43:09.830 回答