编辑:这个问题的要求已经改变。请参阅下面的更新部分。
我有一个异步迭代器方法,它每 200 毫秒产生一个IAsyncEnumerable<int>
(数字流)一个数字。此方法的调用者使用流,但希望在 1000 毫秒后停止枚举。因此CancellationTokenSource
使用了 a,并且令牌作为参数传递给WithCancellation
扩展方法。但是令牌不受尊重。枚举一直持续到所有数字都被消耗完:
static async IAsyncEnumerable<int> GetSequence()
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(200);
yield return i;
}
}
var cts = new CancellationTokenSource(1000);
await foreach (var i in GetSequence().WithCancellation(cts.Token))
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > {i}");
}
输出:
12:55:17.506 > 1
12:55:17.739 > 2
12:55:17.941 > 3
12:55:18.155 > 4
12:55:18.367 > 5
12:55:18.570 > 6
12:55:18.772 > 7
12 :55:18.973 > 8
12:55:19.174 > 9
12:55:19.376 > 10
预期的输出是TaskCanceledException
在数字 5 之后发生。看来我误解了WithCancellation
实际在做什么。该方法只是将提供的令牌传递给迭代器方法,如果该方法接受一个。否则,就像GetSequence()
我的示例中的方法一样,令牌将被忽略。我想我的解决方案是手动查询枚举体内的令牌:
var cts = new CancellationTokenSource(1000);
await foreach (var i in GetSequence())
{
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > {i}");
}
这很简单并且效果很好。但无论如何,我想知道是否有可能创建一个扩展方法来做我期望WithCancellation
做的事情,在随后的枚举中烘焙令牌。这是所需方法的签名:
public static IAsyncEnumerable<T> WithEnforcedCancellation<T>(
this IAsyncEnumerable<T> source, CancellationToken cancellationToken)
{
// Is it possible?
}
更新:似乎当我问这个问题时,我对整个取消概念的目的有一个不正确的理解。我的印象是取消是为了在等待之后打破循环MoveNextAsync
,而真正的目的是取消等待本身。在我的简单示例中,等待仅持续 200 毫秒,但在现实世界的示例中,等待可能更长,甚至是无限的。意识到这一点后,我现在的问题几乎没有价值,我必须要么删除它并打开一个具有相同标题的新问题,要么更改现有问题的要求。这两种选择都以某种方式不好。
我决定选择第二个选项。因此,我不接受当前接受的答案,并且我正在寻求一种新的解决方案,以解决以立即生效的方式执行取消的更困难的问题。换句话说,取消令牌应该会导致异步枚举在几毫秒内完成。让我们举一个实际的例子来区分合意和不合意的行为:
var cts = new CancellationTokenSource(500);
var stopwatch = Stopwatch.StartNew();
try
{
await foreach (var i in GetSequence().WithEnforcedCancellation(cts.Token))
{
Console.WriteLine($"{stopwatch.Elapsed:m':'ss'.'fff} > {i}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine($"{stopwatch.Elapsed:m':'ss'.'fff} > Canceled");
}
输出(理想):
0:00.242 > 1
0:00.467 > 2
0:00.500 > 取消
输出(不良):
0:00.242 > 1
0:00.467 > 2
0:00.707 > 取消
GetSequence
与初始示例中的方法相同,每 200 毫秒传输一个数字。此方法不支持取消,前提是我们无法更改。WithEnforcedCancellation
是应该解决此问题的必需扩展方法。