我一直在研究一个在频道中排队耗时的工作的功能,在那里我使用例如迭代频道
await foreach(var item in channel.Reader.ReadAllAsync(cancellationToken)) {...}
我期待当通过 that 请求取消时cancellationToken
,ReadAllAsync
会在取消之后的第一次迭代中抛出。
在我看来,情况并非如此。循环一直持续到处理完所有项目,然后抛出一个OperationCanceledException
.
这看起来有点奇怪,至少可以这么说。从ChannelReader
的github 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