【C#】关于异步循环(IAsyncEnumerable)的超时时

2022-10-25  本文已影响0人  冰麟轻武

今天在做异步循环的时候发现一个很有意思的事情
先看代码

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();

``
上一篇 下一篇

猜你喜欢

热点阅读