今天在做异步循环的时候发现一个很有意思的事情
先看代码
public static async IAsyncEnumerable<string> Test([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
for (int i = 0; i < n ; i++)
{
await Task.Delay(1000, cancellationToken);
yield return i.ToString();
}
}
这里模拟一个异步循环,每次循环延迟1秒,n
表示不确定会循环多少次,有一个参数是取消标记
下面调用这个方法
var source = new CancellationTokenSource(3000);
await foreach (var item in Test().WithCancellation(source.Token))
{
Console.WriteLine(item);
}
执行结果
打印2次后超时报错
这是个很有啥意思的地方是:大部分情况下我不确定会循环多少次,所以没办法给定一个固定的超时时间
但我希望每次不超过1.5秒,使用CancellationTokenSource.TryReset()
可达到这个效果
代码如下:
var source = new CancellationTokenSource(1500);
await foreach (var item in Test().WithCancellation(source.Token))
{
source.TryReset();
Console.WriteLine(item);
}
Console.ReadLine();
确实可以完整输出
但是,如果我又希望他可以控制总时间怎么办呢?
很简单再加一个 CancellationTokenSource
代码如下:
var step = new CancellationTokenSource(1500);
var total = new CancellationTokenSource(5000);
var source = CancellationTokenSource.CreateLinkedTokenSource(step.Token, total.Token);
await foreach (var item in GitHelper.Test().WithCancellation(source.Token))
{
step.TryReset();
Console.WriteLine(item);
}
Console.ReadLine();
输出:
总时间超过5秒超时
单次执行时间不足1.5秒所以每次单步都不超时,但总时间超过了5秒所以总时间超时了
达到了预期的效果
为了严谨,修改下单步时间试试
直接超时了
但是!对于异步循环来说这是一个非常常见的场景,所以把他封装成一个扩展方法方便调用
public static async IAsyncEnumerable<string> WithTimeout(this IAsyncEnumerable<string> enumerable, int totalMilliseconds, int stepMilliseconds = 0)
{
using var step = stepMilliseconds <= 0 ? null : new CancellationTokenSource(stepMilliseconds);
using var total = totalMilliseconds <= 0 ? null : new CancellationTokenSource(totalMilliseconds);
using var source = total.LinkedTokenSource(step);
await foreach (var item in enumerable.WithCancellation(source?.Token ?? default))
{
step?.TryReset();
yield return item;
}
}
public static CancellationTokenSource? LinkedTokenSource(this CancellationTokenSource? source1, CancellationTokenSource? source2)
{
if (source1 is not null && source2 is not null)
{
return CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
}
return source1 ?? source2;
}
实际使用中调用WithTimeout
方法,设置参数,第一个参数表示总超时时间,第二个参数表示单步超时时间
调用也很方便
await foreach (var item in Test().WithTimeout(5000, 1500))
{
Console.WriteLine(item);
}
Console.ReadLine();
``
网友评论