目录

一、需求

二、Task取消任务

三、Task取消任务的回调

四、Task超时处理的实现

五、Task.WhenAny 的异常

六、其他的写法

结束


一、需求

在之前的帖子中,介绍了 async / await 的用法,那么新的问题又来了,如果调用一个异步方法后,一直不给返回值结果怎么办呢?这就涉及到怎么取消任务了。添加一个任务后,如果固定时间内没用返回结果,那么就取消执行,并且在多个任务同时执行的时候,依然按顺序来执行,这就是本文章要实现的功能。

二、Task取消任务

先介绍一下 Task 结束任务的传统用法。在 C# 以前的2.0 等版本中,线程是可以强制终止的,到了后面,就不允许强制去结束线程的,后面微软就提供了一个 CancellationTokenSource 相关的接口开源取消任务。于是我搜索了大量的帖子,一看全是各种抄袭,相互抄来抄去的,搞的我真的火冒三丈,完全是浪费时间,那么总结取消的方法如下:

namespace 取消任务3
{internal class Program{static CancellationTokenSource source = new CancellationTokenSource();static void Main(string[] args){Task.Run(() =>{for (int i = 0; i < 10; i++){Thread.Sleep(100);Console.WriteLine("oh my god");source.Token.ThrowIfCancellationRequested();}}, source.Token);Thread.Sleep(2000);Console.WriteLine("取消任务");source.Cancel();Console.ReadKey();}}
}

运行:

这个可以取消任务是吧,那下面换一个写法:

namespace 取消任务3
{internal class Program{static CancellationTokenSource source = new CancellationTokenSource();static void Main(string[] args){Task.Run(() =>{Thread.Sleep(3000);Console.WriteLine("oh my god");source.Token.ThrowIfCancellationRequested();}, source.Token);Thread.Sleep(1000);Console.WriteLine("取消任务");source.Cancel();Console.ReadKey();}}
}

任务中等待三秒,我在等待一秒后取消任务,看看结果:

这回就不管用了,任务明明取消了,但结果依然执行了。

根据他们写的例子,可以总结一点,就是要想取消任务,在任务中,必须加入 for 或者 while 循环,并且在下一轮循环中,执行到 source.Token.ThrowIfCancellationRequested() 这句才能取消任务。

换个写法,如果非得用 for 或者 while 循环这样的语法才能取消任务,我在 while 循环中加入一个判断,如果等于 true,直接跳出循环,这不也是中断了任务,所以说 CancellationTokenSource 真的意义不大

namespace 取消任务4
{internal class Program{static void Main(string[] args){bool isOut = false;var task1 = Task.Run(() =>{for (int i = 0; i < 100; i++){if (isOut) return;Console.WriteLine("执行中" + i);Thread.Sleep(500);}});Thread.Sleep(2000);Console.WriteLine("取消任务");isOut= true;Console.ReadKey();}}
}

运行:

三、Task取消任务的回调

取消任务也是可以加入回调的,如下:

namespace 取消任务2
{internal class Program{static CancellationTokenSource source = new CancellationTokenSource();static void Main(string[] args){var task1 = Task.Run(() =>{for (int i = 0; i < 100; i++){source.Token.ThrowIfCancellationRequested();Console.WriteLine("执行中" + i);Thread.Sleep(500);}}, source.Token);//在指定的毫秒数后取消task执行source.CancelAfter(2 * 1000);//取消任务后的回调source.Token.Register(() =>{//不延迟会获取不到正确的状态Thread.Sleep(50);Console.WriteLine("task1状态:" + task1.Status);Console.WriteLine("IsFaulted状态:" + task1.IsFaulted);//由于未处理的异常,任务已完成。Console.WriteLine("IsCompleted状态:" + task1.IsCompleted);//获取一个值,该值指示任务是否已完成。});Console.ReadKey();}}
}

运行:

四、Task超时处理的实现

在上面的介绍中可以看到,Task取消任务传统的用法并不好用,必须在里面加上条件判断,如果满足条件就跳出 for 或者 while 循环,达到方法执行完成的目的,而并不是真的终止了任务。

那么这么需求要如何去完成呢,微软官方也提供了一个叫 Task.WhenAny 接口,可以实现这个功能,下面就看看如何实现的。

新建一个基于 .Net6 的 Winform 项目,新建一个脚本 Lib.cs

namespace Utils
{public static class Lib{public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult>? successor = null){//用于取消任务using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource();//WhenAny 等待所有任务结束,这里加入了超时时间//ConfigureAwait 配置用来等待 任务1的警报,返回值可以获取到改任务Task? completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);//如果当前任务完成了,并且匹配if (completedTask == task){//取消任务timeoutCancellationTokenSource.Cancel();//得到任务返回结果var result = await task.ConfigureAwait(continueOnCapturedContext: false);//执行回调if(successor != null) successor(result);return true;}else //任务超时return false;}}
}

界面如下,就几个按钮

代码:

using Utils;namespace 异步编程
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){Test1();}private void button2_Click(object sender, EventArgs e){Test2();}private void Button_ClearConsole_Click(object sender, EventArgs e){Console.Clear();}private async void Test1(){var task1 = Task.Run(() =>{Thread.Sleep(3000);return "task1";});bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>{Console.WriteLine("-----------------------task1回调:" + msg);});string isTimeout1 = res1 == true ? "没超时" : "超时";Console.WriteLine("任务1:" + isTimeout1);var task2 = Task.Run(() =>{Thread.Sleep(3000);return "task2";});bool res2 = await task2.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>{Console.WriteLine("-----------------------task2回调:" + msg);});string isTimeout2 = res2 == true ? "没超时" : "超时";Console.WriteLine("任务2:" + isTimeout2);}private async void Test2(){var task3 = Task.Run(() =>{Thread.Sleep(3000);return "task3";});bool res3 = await task3.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>{Console.WriteLine("-----------------------task3回调:" + msg);});string isTimeou3 = res3 == true ? "没超时" : "超时";Console.WriteLine("任务2:" + isTimeou3);}}
}

按钮1和按钮2 方法里有三个异步方法,超时时间都是4秒,也就是说,如果方法在4秒之内没有返回值则为失败。

分别点击按钮1,按钮2

在按钮1方法里有两个异步方法,异步方法1执行完成后,才能执行异步方法2,所以异步方法2要比异步方法3更慢一些。

下面就将三个异步方法的超时时间改为1秒,看看效果:

返回超时,而且任务也没有执行,这样就实现了我们的想要的效果了。

五、Task.WhenAny 的异常

在一系列的测试中,我发现了 Task.WhenAny 这个接口在 .Net6 的控制台项目的异常之处,执行一次是正常的,如果在一个方法内同时执行多次,返回结果就开始乱了,在 Winform 项目中是没有这种事的,下面开始演示。

新建一个基于 .Net6 的控制台项目, 将上面的 Lib.cs 代码复制到项目中来。

代码:

using Utils;namespace 异步编程2
{internal class Program{static void Main(string[] args){AwaitReturnValue();Console.ReadKey();}public static async void AwaitReturnValue(){var task1 = Task.Run(() =>{Thread.Sleep(3000);return "task1";});for (int i = 0; i < 10; i++){bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), (string msg) =>{Console.WriteLine("task1回调:" + msg);});string isTimeout = res1 == true ? "没超时" : "超时";Console.WriteLine(string.Format("结果{0}:{1}", i, isTimeout));}}}
}

运行:

任务前两次是正确的,后面返回的基本全是错误的,原因我估计是 Task 任务内部等待时间是3秒,调用了前两次时间没有超过三秒,所以返回是正确的,后面超过3秒后,全当在超时范围内返回了。

六、其他的写法

超时取消任务的写法可以有多种,其实万变不离其宗,都是用 Task.WhenAny 方法实现的,代码我全部放一个类里面了,有兴趣的可以看看,有很多的高级语法,确实是值得学习的。

代码:

namespace 异步编程1
{public static class Lib1{public static async Task<TResult?> TimeoutAfter<TResult>(this Task<TResult> task, int timeout){using (var cancelToken = new CancellationTokenSource()){Task completedTask = await Task.WhenAny(task, Task.Delay(timeout, cancelToken.Token));if (completedTask == task){cancelToken.Cancel();return await task;}else{// 超时处理Console.WriteLine("超时了");return default;}}}public static async Task<bool> OnTimeout<T>(T t, Action<T> action, int waitms) where T : Task{if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t)){action(t);return true;}else{return false;}}public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult> successor){using var timeoutCancellationTokenSource = new CancellationTokenSource();var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);if (completedTask == task){timeoutCancellationTokenSource.Cancel();// propagate exception rather than AggregateException, if calling task.Result.var result = await task.ConfigureAwait(continueOnCapturedContext: false);successor(result);return true;}elsereturn false;}public static async Task<bool> BeforeTimeout(Task task, int millisecondsTimeout){if (task.IsCompleted) return true;if (millisecondsTimeout == 0) return false;if (millisecondsTimeout == Timeout.Infinite){await Task.WhenAll(task);return true;}var tcs = new TaskCompletionSource<object>();using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs, millisecondsTimeout, Timeout.Infinite)){return await Task.WhenAny(task, tcs.Task) == task;}}}
}

结束

如果这个帖子对你有所帮助,欢迎 关注 、点赞 、留言

end

C# async / await 任务超时处理相关推荐

  1. async function_掌握 Async/Await

    摘要: 还不用Async/Await就OUT了.. 原文:掌握 Async/Await 作者:Jartto Fundebug经授权转载,版权归原作者所有. 前端工程师肯定都经历过 JS 回调链狱的痛苦 ...

  2. 深入async/await知多少

    .net的async/await功能相信对很多人来说并不陌生了,有人感觉这功能很好,但也有人说这功能不好容易产生一些莫名其妙的死锁:有人说这些异步功能也有人说这是同步功能.其实在使用async/awa ...

  3. 【转】异步编程系列(Thread、Task、async/await、ajax等)

    序 经过一番努力,我写的异步编程系列也算有头有尾,当然不是说这个系列已经更新完毕,这个头尾只是表示新旧知识点都有简单涉及到,接下去我还会丰富这一系列并且有机会整个小应用(愿景是弄一个开源组件吧,结合s ...

  4. Python 异步 IO 、协程、asyncio、async/await、aiohttp

    From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...

  5. axios的二次封装与async,await的配合使用?

    前言:前些日子读文章,说是vue3.0会在明年下半年正式推出,改动的地方好像也不少,比如说vue3.x的代码库将会用typescript编写,并提供改进的 TypeScript 支持.变化还是很快的, ...

  6. JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧...

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  7. [译] JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧...

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  8. 【面试Vue全家桶】vue前端交互模式-es7的​语法结构?async/await​

    作者 |  Jeskson 掘金 |  https://juejin.im/user/5a16e1f3f265da43128096cb 2020.1.12 前端发请求,调用后端接口,来获取特定格式的数 ...

  9. setTimeout、setInterval、promise、async/await的顺序详解(多种情况,非常详细~)

    本文很长,列举的情况很多. 在阅读本文之前,如果您有充足的时间,请新建一个项目与本文一同实践. 每段代码都有对应的解释,但是自己动手尝试印象才会更深哦~ setInterval:表示多久执行一次,需要 ...

  10. C# async await 学习笔记2

    C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html) 提到了ThreadId是一样的,突然想到在WinForm中,非UI线程 ...

最新文章

  1. 2019研究生新生大数据出炉!清华园迎来8900多名新主人
  2. Windows 7 + Fedora 17 双系统安装详解
  3. 【Java核心面试宝典(2),记一次美团Java研发岗的面试经历
  4. mysql dba系统学习(4)mysql的多实例multi启动停止
  5. Python 数据分析与展示笔记4 -- Pandas 库基础
  6. VC的Win32控制台程序中使用MFC库文件
  7. Codrops 优秀教程:基于 CSS3 的全屏网页过渡特效
  8. php 简析对象,PHP白盒审计工具RIPS源码简析
  9. 【Python】求n!
  10. 数据库性能自动压测-Oracle swingbench篇
  11. python按键盘上哪个键运行_python按什么键运行
  12. Linux命令详解词典高频命令(2)
  13. STM32串行驱动LCD12864显示屏程序代码
  14. HTML前端数据管理,前端数据管理
  15. [刘润五分钟商学院]-----工具类
  16. opencv cvtColor 出错
  17. 联想服务器id显示感叹号,网卡驱动安装后显示感叹号
  18. 计算机为什么要用补码?
  19. Spark的坑--Spark新手必看--Python Spark必读,耗费了我近三周的时间
  20. 视频教程-Java面试Offer直通车-Java

热门文章

  1. android的补间动画一共包含,Android的四种补间动画
  2. 斯坦福大学终身教授张首晟:区块链最核心的理念,必然是「 In Math We Trust 」
  3. [下载] Windows 10 Version 1809 微软官方原版镜像
  4. Linux控制Nvidia显卡风扇转速
  5. c语言spoc测验成绩比重,SPOC混合教学模式在C语言程序设计课程的应用
  6. 计算机硬件具体都包含哪些,电脑硬件包括哪些
  7. jsp实现页面自动跳转
  8. idea无法下载源代码
  9. csma研究背景_以太网CSMA_CD工作原理研究
  10. 年龄怎么用计算机算,年龄计算器