一次.net core异步线程设置超时时间的实战记录

2022-04-17 00:01:43
目录
前言:需求背景:需求提炼:.net里设置超时的 Task测试代码测试截图搞定收工总结

前言:

刷帖子看到一篇 Go 记录一次groutine通信与context控制 看了一下需求背景,挺有意思的,琢磨了下.net core下的实现

需求背景:

项目中需要定期执行任务A来做一些辅助的工作,A的执行需要在超时时间内完成,如果本次执行超时了,那就不对本次的执行结果进行处理(即放弃这次执行)。同时A又依赖B,C两个子任务的执行结果。B, C之间相互独立,可以并行的执行。但无论B,C哪一个执行失败或超时都会导致本次任务执行失败。

需求提炼:

A任务必须在指定时间内完成,否则任务失败A任务依赖B,C任务,B,C可以并行,任何一个失败,则A任务失败A任务在超时时间内,是否需求记录子任务执行详情(根据业务需求来定)

.net里设置超时的 Task

public static class TaskHelper{    // 有返回值    public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)    {        using (var timeoutCHbXADGTTancellationTokenSource = new CancellationTokenSource())        {            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));            if (completedTask == task)            {                timeoutCancellationTokenSource.Cancel();                return await task;  // Very important in order to propagate exceptions            }            else            {                throw new TimeoutException("The operation has timed out.");            }        }    }    // 无返回值    public static async Task TimeoutAfter(this Task task, TimeSpan timeout)    {        using (var timeoutCancellationTokenSource = new CancellationTokenSource())        {            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));            if (completedTask == task)            {                timeoutCancellationTokenSource.Cancel();                await task;  // Very important in order to propagate exceptions            }            else            {                throw new TimeoutException("The operation has timed out.");            }        }    }}

这里参考资料,写了个拓展方法,主要用到CancellationTokenSource 与 Task.WhenAny

可以参考 C#中CancellationToken和CancellationTokenSource用法

可以参考 Task.WhenAny 方法

这里需要特别注意,在异步操作里,如果异步已经执行了,再执行取消时无效的,这是就需要我们自己在异步委托中检测了

知道了这两个函数的作用,这段代码就很好理解了,通过Task.WhenAny返回最先完成的任务,如果是业务任务先完成,则调用timeoutCancellationTokenSource.Cancel()终止超时任务,等待业务任务结果,反之则直接抛出timeout异常

测试代码

    [TestMethod]    public async Task TestMethod1()    {        //A 任务必须在指定时间内完成,否则任务失败        //A 任务依赖B,C任务,B,C可以并行,任何一个失败,则A任务失败        //A任务        try        {            //有效时间3s            var timeOut = TimeSpan.FromSeconds(3);            await Task.Run(async () =>            {                List<Task<(string, bool)>> tasks = new List<Task<(string, bool)>>();                //B任务                tasks.Add(Task.Run(async () =>                {                    return ("B", await TestTask("B"));                }).TimeoutAfter(timeOut));                //C任务                tasks.Add(Task.Run(async () =>                {                    return ("C", await TestTask("C"));                }).TimeoutA需要我们自己在异步委托中检测了

如果写了一个死循环的task,那后果将不堪设想,这个时候,就需要慎重了,在使用多线程取消令牌的时候,除了需要执行Cancel()方法,还需要在子任务内自己捕获CancellationTokenSource.Token.ThrowIfCancellationRequested()

总结