聊聊多线程哪一些事儿(task)之 三 异步取消和异步方法
hello,咋们又见面啦,通过前面两篇文章的介绍,对task的创建、运行、阻塞、同步、延续操作等都有了很好的认识和使用,结合实际的场景介绍,这样一来在实际的工作中也能够解决很大一部分的关于多线程的业务,但是只有这一些是远远不够的,比如,比如,如果这么一个场景,当开启tsak异步任务后,有某个条件触发,需要终止tsak的执行又该如何实现呢?这一些问题正是我们今天需要交流分享的部分,带着这一些问题,咱们共同进入到今天的主题,谢谢!
在进入主题前,如果你没有阅读前面的两篇文章,欢迎您点击下面地址先阅读一下,这样能够更加连贯的掌握了解今天的内容,谢谢!
第一篇:聊聊多线程哪一些事儿(task)之 一创建运行与阻塞
第三篇:聊聊多线程哪一些事儿(task)之 三 异步取消和异步方法
task之任务取消:cancellationtokensource
关于线程取消,我相信大家在实际工作中都会遇到这样的问题,无论是采用哪一种方式实现异步线程,都会有相应的机制来取消线程操作。本次将同时对thread的线程取消实现,tsak的线程取消实现同时通过实例说明。
在我的工作经验中,需要取消异步线程作业的实际使用场景往往是一些异步作业程序,也就是一些周期性的,循环业务操作。比如周期性的数据同步、数据更新等等操作。比如:电商系统常见的一个场景,订单超时取消等等。
为了与前两篇的实例保持一致性,我现在还是以酒店平台的数据同步业务为例:
需求:每周三凌晨3点钟,通过携程提供的酒店分页查询接口,全量同步一次最新的酒店数据。并且能够通过人为的干预来终止数据同步操作。
下面我将分别通过thread和task两种方式来实现
其一、thread时代之任务取消
哈哈,实话实话说,在几年前的项目中,我也是采用thread来实现异步线程的,也会遇到线程的取消的业务场景。我当时的实现方式是,定义一个全局变量,isstopthread(是否终止线程),去过需要取消任务,只需要控制isstopthread的值即可,每一次执行具体的业务时,首先判断一下isstopthread,只有非终止状态才执行具体的业务逻辑。
/// <summary> /// 携程 酒店数据同步作业(thread) /// </summary> private static void ctriphotedatasynchrbythread() { cancellationtokensource cancellationtokensource = new cancellationtokensource(); // 第一步通过thread开启一个线程 thread thread = new thread(() => { // 获取数据的次数 int getdataindex = 1; isstopctriphotedatasynchr = false; // 通过调用携程的分页服务,获取其有效的酒店数据 // 在获取数据前,首先判断一下是否终止获取 while (!cancellationtokensource.iscancellationrequested) { // 现在假设模拟,获取携程的所有有效的酒店数据通过 3 次就获取完毕 console.writeline($"开始获取携程第 {getdataindex} 页酒店数据....\n"); thread.sleep(3000); console.writeline($"携程第 {getdataindex} 页酒店数据获取完毕\n"); getdataindex++; // 模拟获取完第三页数据,代表数据获取完毕,直接终止掉 if (getdataindex == 4) { console.writeline($"同步完毕携程的所有酒店数据\n"); break; } } if (isstopctriphotedatasynchr) { console.writeline($"取消同步携程酒店数据\n"); } }); thread.start(); console.writeline("携程酒店数据同步中.....\n"); // 模拟实际数据同步中的取消操作 console.writeline("如果需要取消数据同步,那么请输入任意字符即可取消操作\n"); console.readline(); cancellationtokensource.cancel(); isstopctriphotedatasynchr = true; console.writeline($"发起取消同步携程酒店数据请求\n"); }
执行结果:
通过测试结果我们可以看到,在获取第2页数据时,此时发起了一个取消线程命令,当第二页数据获取完毕后,线程就里面终止了,从而到达了线程取消的目的。
其二、task时代之任务取消
随着task的推出,微软也推出了一个专门服务于线程取消的帮助类(cancellationtokensource),通过该类能够很好的帮助我们取消一个线程,话不多说,我们先通过cancellationtokensource类实现上面示例的功能。
/// <summary> /// 携程 酒店数据同步作业(task) /// </summary> private static void ctriphotedatasynchrbytask() { // 定义任务取消机制 cancellationtokensource cancellationtokensource = new cancellationtokensource(); // 第一步通过thread开启一个线程 task thread = new task(() => { // 获取数据的次数 int getdataindex = 1; // 通过调用携程的分页服务,获取其有效的酒店数据 // 在获取数据前,首先判断一下是否终止获取 while (!cancellationtokensource.iscancellationrequested) { // 现在假设模拟,获取携程的所有有效的酒店数据通过 3 次就获取完毕 console.writeline($"开始获取携程第 {getdataindex} 页酒店数据....\n"); console.writeline($"携程第 {getdataindex} 页酒店数据获取完毕\n"); getdataindex++; // 模拟获取完第三页数据,代表数据获取完毕,直接终止掉 if (getdataindex == 4) { console.writeline($"同步完毕携程的所有酒店数据\n"); break; } } if (cancellationtokensource.iscancellationrequested) { console.writeline($"取消同步携程酒店数据\n"); } }); thread.start(); console.writeline("携程酒店数据同步中.....\n"); // 模拟实际数据同步中的取消操作 console.writeline("如果需要取消数据同步,那么请输入任意字符即可取消操作\n"); console.readline(); // 直接取消线程 cancellationtokensource.cancel(); // 在指定时间后取消线程 // cancellationtokensource.cancelafter(1000); console.writeline($"发起取消同步携程酒店数据请求\n"); }
测试结果:
通过测试结果,两种实现方式的结果完全一致
当然,cancellationtokensource 还提供了cancelafter(多久后取消)方法,来实现多久后取消线程。
说到这儿,不知道大家有没有发现一个问题cancellationtokensource 其实现是不是与task和thread没有多少关系,在第一个实例中通过thread实现的线程取消,同样可以结合cancellationtokensource 来实现。所以说,在开始我说cancellationtokensource 是微软提供的一个线程取消的一个帮助类就是这个原因。其实我可以打开cancellationtokensource 的实现源码,其实我们就会一目了然,其取消线程的核心逻辑和我们上面的说thread取消的原理很类似,都是控制一个变量的值来实现,只是cancellationtokensource 对其所有操作进行了一个封装。其中的cancelafter里面是开启了一个定义器,定时器的最终实现还是和canel一样。如果想看cancellationtokensource的源码,大家可以查看下面地址:https://www.cnblogs.com/majiang/p/7920102.html
最后需要说明的是task与cancellationtokensource都是.net framework4.0+、.net core、.net standard。
异步方法之:(async/await)
c#5.0微软推出了一个新的特性那就是异步方法,其关键词为async。有了async我们要实现一个异步方法就简单的多啦,你会发现和实现一个同步方法很相似,只需要对方法加以async修饰即可。当然如果只是简单的修饰调用,那么也会是同步调用,为了达到真正的异步调用,往往是需要另外一个关键词await来配合使用。
先简单介绍一下async异步函数:
async的三种返回类型:
tsak:其主要适用场景是,主程序只关心异步方法执行状态,不需要和主线程有任何执行结果数据交互。
task<t>:其主要适用场景是,主程序不仅仅关心异步方法执行状态,并且还希望执行后返回一个数据类型为t的结果
void: 主程序既不关系异步方法执行状态,也不关心其执行结果,只是主程序调用一次异步方法,对于除事件处理程序以外的代码,通常不鼓励使用 async void 方法,因为调用方不能
在介绍一下await关键词:
await其顾名思义就是等待的意思,其运行原理就是:调用方执行到await时就会立即返回,但是异步方法等待异步执行结果。所以await只能存在于async修饰的异步方法体中,await不阻塞主线程,只是阻塞当前异步方法继续往下执行,这样就能够达到真正异步的目的。
下面以一个简单的例子来说明一下每一种情况的使用:
static void main(string[] args) { console.writeline("主线程开始\n"); console.writeline("主线程调用同步方法:syntest\n"); syntest(); console.writeline("主线程调用异步方法:asynctestnoawait\n"); asynctestnoawait(); console.writeline("主线程调用异步方法:asynctesthasawait\n"); asynctesthasawait(); console.writeline("主线程结束\n"); console.readkey(); } /// <summary> /// 同步方法测试 /// </summary> public static void syntest() { console.writeline("同步方法syntest开始运行\n"); thread.sleep(5000); console.writeline("同步方法syntest运行结束\n"); } /// <summary> /// 异步方法测试(不带有 await关键词) /// </summary> public static async void asynctestnoawait() { console.writeline("异步方法asynctestnoawait开始运行\n"); thread.sleep(5000); console.writeline("异步方法asynctestnoawait运行结束\n"); } /// <summary> /// 异步方法测试(带有 await关键词) /// </summary> public static async void asynctesthasawait() { console.writeline("异步方法asynctesthasawait开始运行\n"); await task.delay(5000); console.writeline("异步方法asynctesthasawait运行结束\n"); }
运行结果:
从运行结果我们可以很好的得出:
1、异步方法async如果没有await关键词,其执行原理还是同步调用
2、await关键词只能存在云async修饰的方法体中
3、异步方法async在调用时,只有遇到await关键词后的程序块才是异步执行,其await关键词前的代码块还是同步执行
好了,管理async先介绍到这儿,由于时间和文章篇幅原因,就不在详细介绍,里面还有很多内容需要注意,后续在根据实际做一个async/await的专题文章。
总结:
到目前为止,有关task的3篇文章都到此结束,下面在回顾总结一下task的相关功能点吧!
1、task的创建运行可以有三种方式:new task/task.factory/task.run
2、task的返回参数定义task<返回类型>
获取返回值:task.result->要阻塞主流程
3、task线程的同步实现不仅仅可以通过runsynchronously来实现同步运行,当然还可以通过task.result/task.wait等方式来变向实现
4、task的wait/waitall/waitany实现阻塞等待执行结果
5、task的whenany、whenall、continuewith实现延续操作
6、cancellationtokensource实现异步任务取消
7、异步方法之:(async/await)实现同步和异步调用等
猜您喜欢:
第一篇:聊聊多线程哪一些事儿(task)之 一创建运行与阻塞
end
为了更高的交流,欢迎大家关注我的公众号,扫描下面二维码即可关注,谢谢:
上一篇: 记一次画图出现的小细节,导致我找了3天多