前言:
刷帖子看到一篇 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 timeoutCancellationTokenSource = 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")); }).TimeoutAfter(timeOut)); var res = await Task.WhenAll(tasks); //两个任务,任何一个失败,则A任务失败 foreach (var item in res) { Console.WriteLine(item); } }).TimeoutAfter(timeOut); } catch (Exception ex) { Console.WriteLine("A任务执行超时了"); } //await Task.Delay(3000); } public async Task<bool> TestTask(string name) { var startTime = DateTime.Now; Console.WriteLine($"{startTime}---->{name}任务开始执行"); //随机堵塞1-5s var t = new Random().Next(1, 5); await Task.Delay(t * 1000); var endTime = DateTime.Now; ; var time = (endTime - startTime).TotalSeconds; //随机数,模拟业务是否成功 var res = new Random().Next(1, 10); Console.WriteLine($"{endTime}---->{name}任务执行完毕,耗时{time} s"); return res <= 7; }
测试截图
搞定收工
故事在这里就结束了吗? 显然没有,这么简单也没必要水一篇博客了
我们能做到在3s内响应结果,也算基本上满足了需求,那超时的子任务,是否会继续执行呢?
仔细看代码,就算超时,也是停止的Task.Delay() 这个线程,与业务线程没有半毛钱关系,那业务线程肯定会继续执行
眼尖的同学已经看到最后一张图,B任务执行了3.0076088s,按道理B任务是已经超时了,这段话是不会输出的,那如果我让主线程晚点退出,那超时的子线程是否能正常执行, //await Task.Delay(3000); 将这段代码取消注释,再来观看结果
有没有一种被欺骗的感觉,写了一个假的超时时间,哈哈哈哈.....
这里需要特别注意,在异步操作里,如果异步已经执行了,再执行取消时无效的,这是就需要我们自己在异步委托中检测了
如果写了一个死循环的task,那后果将不堪设想,这个时候,就需要慎重了,在使用多线程取消令牌的时候,除了需要执行Cancel()方法,还需要在子任务内自己捕获CancellationTokenSource.Token.ThrowIfCancellationRequested()
总结
到此这篇关于一次.net core异步线程设置超时时间的文章就介绍到这了,更多相关.net core异步线程设置超时时间内容请搜索阿兔在线工具以前的文章或继续浏览下面的相关文章希望大家以后多多支持阿兔在线工具!