C# 多线程学习系列四之ThreadPool取消、超时子线程操作以及ManualResetEvent和AutoResetEvent信号量的使用...

1、简介

虽然ThreadPool、Thread能开启子线程将一些任务交给子线程去承担,但是很多时候,因为某种原因,比如子线程发生异常、或者子线程的业务逻辑不符合我们的预期,那么这个时候我们必须关闭它,而不是让它继续执行,消耗资源.让CPU不在把时间和资源花在没有意义的代码上.

 

2、主线程取消所有子线程执行的简单代码演示和原理分析

(1)、代码演示

        static void Main(string[] args){//显示定义一个取消辅助线程的操作CancellationTokenSource ctsToken = new CancellationTokenSource();ThreadPool.QueueUserWorkItem(o => EoworkOne(ctsToken.Token));ThreadPool.QueueUserWorkItem(o => EoworkTwo(ctsToken.Token));ctsToken.Cancel();Console.Read();}/// <summary>/// 辅助线程一/// </summary>/// <param name="token"></param>static void EoworkOne(CancellationToken token){//判断主线程是否调用了CancellationTokenSource实例的Cancel方法//相当于判断主线程是否传递给辅助线程一一个取消标记,如果你去看源码,你会发现,里面有个有趣的类Timer,so,你懂的!结合之前的文档,可以猜测这个时间很有可能是CPU切换上线文的时间
//每当过了这个时间,该子线程就去判断主线程有没有传递给它取消的信号.当然这只是我的猜测,哈哈
if (token.IsCancellationRequested){//如果主线程传递给辅助线程一一个取消操作标记,执行下面的代码Console.WriteLine("主线程调用了Cancel方法,所以辅助线程一获取了主线程取消辅助线程一的标记,但是并不会真正的关闭当前线程");Console.WriteLine("辅助线程一执行return操作,自己显示的退出,那么接下去的方法都不会被执行");return;}}/// <summary>/// 辅助线程二/// </summary>/// <param name="token"></param>static void EoworkTwo(CancellationToken token){//判断主线程是否调用了CancellationTokenSource实例的Cancel方法//相当于判断主线程是否传递给辅助线程一一个取消标记if (token.IsCancellationRequested){//如果主线程传递给辅助线程一一个取消操作标记,执行下面的代码Console.WriteLine("主线程调用了Cancel方法,所以辅助线程二获取了主线程取消辅助线程二的标记,但是并不会真正的关闭当前线程");}//因为当主线程传递给辅助线程二一个取消标记,但是上面的if语句块,并没有执行return操作,所以下面的语句还是会继续执行Console.WriteLine("辅助线程二获得取消标记操作后,并没有执行显示的return操作,所以辅助线程二继续执行");}

 

(2)、原理分析

 第一步:创建一个CancellationTokenSource对象实例,该对象包含了所有关于取消子线程有关的所有状态

CancellationTokenSource ctsToken = new CancellationTokenSource();

 第二步:将CancellationTokenSource对象实例的CancellationToken对象实例传递给需要进行取消操作的所有子线程.并且可以通过这个CancellationToken对象实例关联到CancellationTokenSource对象实例.

ThreadPool.QueueUserWorkItem(o => EoworkOne(ctsToken.Token));
ThreadPool.QueueUserWorkItem(o => EoworkTwo(ctsToken.Token));

 第三步:当主线程调用CancellationTokenSource对象实例的Cancel方法,所有的子线程通过调用CancellationToken对象实例的IsCancellationRequested属性,该属性定时去获取初始线程(主线程)是否执行了CancellationTokenSource对象实例的Cancel方法,如果调用了,该属性为true。这时可以理解为子线程到主线程的取消信号,可以通过调用return方法来终止子线程的操作.

   //判断主线程是否调用了CancellationTokenSource实例的Cancel方法//相当于判断主线程是否传递给辅助线程一一个取消标记if (token.IsCancellationRequested){//如果主线程传递给辅助线程一一个取消操作标记,执行下面的代码Console.WriteLine("主线程调用了Cancel方法,所以辅助线程一获取了主线程取消辅助线程一的标记,但是并不会真正的关闭当前线程");Console.WriteLine("辅助线程一执行return操作,自己显示的退出,那么接下去的方法都不会被执行");return;}

 

3、如果创建一个不能被取消的子线程

通过给子线程传递一个CancellationToken.None实例,该子线程无法被取消,原因很简单,CancellationToken.None实例没有关联的CancellationTokenSource对象实例,所以无法调用Cancel方法显示取消.所以子线程调用token.IsCancellationRequested属性,该属性永远为false.调用token.CanBeCanceled属性也为false.

        static void Main(string[] args){ThreadPool.QueueUserWorkItem(o => EoworkOne(CancellationToken.None));Console.Read();}/// <summary>/// 辅助线程一/// </summary>/// <param name="token"></param>static void EoworkOne(CancellationToken token){if (token.IsCancellationRequested){//永远无法执行
            }Console.WriteLine("辅助线程一能被取消吗?{0}",token.CanBeCanceled?"":"不能");Console.WriteLine("通过CancellationToken.None实例创建的子线程无法被取消");}

 

4、初始线程(主线程)调用给CancellationTokenSource对象实例的Cancel方法添加回调函数

通过调用CancellationToken实例的Register方法来实现这个功能.

        static void Main(string[] args){CancellationTokenSource ctsToken = new CancellationTokenSource();ThreadPool.QueueUserWorkItem((o => eowOne(ctsToken.Token)));ctsToken.Token.Register(() => { Console.WriteLine("ctsToken实例调用Cancel方法之后执行的回调函数一"); });ctsToken.Token.Register(() => { Console.WriteLine("ctsToken实例调用Cancel方法之后执行的回调函数二"); });ctsToken.Cancel();Console.Read();}/// <summary>/// 辅助线程一/// </summary>static void eowOne(CancellationToken token){Thread.Sleep(2000);//模拟处理需要长时间做的任务Console.WriteLine("辅助线程一做完了它的事");}

通过输出,可以发现,在给CancellationTokenSource实例的Token注册完回调函数后,调用CancellationTokenSource实例的Cancel方法,立刻执行回调函数,但是,主线程并没有等子线程执行完毕,在执行注册的回调.而是直接执行回调。说明线程池线程在管理子线程何时执行完毕是非常无力的.

 

5、关于处理CancellationTokenSource实例调用Cancel方法后,获取所有回调函数的未处理的异常

(1)、给CancellationTokenSource的Cancel方法传递true

        static void Main(string[] args){CancellationTokenSource ctsToken = new CancellationTokenSource();ThreadPool.QueueUserWorkItem((o => eowOne(ctsToken.Token)));ctsToken.Token.Register(() => { throw new Exception("回调函数一抛出的异常"); });ctsToken.Token.Register(() => { throw new Exception("回调函数二抛出的异常"); });ctsToken.Cancel(true);Console.Read();}/// <summary>/// 辅助线程一/// </summary>static void eowOne(CancellationToken token){Thread.Sleep(2000);//模拟处理需要长时间做的任务Console.WriteLine("辅助线程一做完了它的事");}

调试代码发现,执行到第一个回调函数,抛出异常,程序直接跳出,不再执行第二个函数.所以可以得出结论,为Cancel方法传递true,它只会捕获第一个异常,不再执行第二个异常.

 

(2)、给CancellationTokenSource的Cancel方法传递false

传递false后,程序会分别执行所有的回调,并抛出一个System.AggregateException异常,回调函数的异常会被追加到到其InnerExceptions属性中.

 

6、ManualResetEvent、AutoResetEvent阻塞线程信号量使用

关于强制主线程等待子线程完成任务之后执行的方法主要用这两个信号量来实现,注意主线程只能等待一个子线程的完成,不能等待两个子线程完成,这里我试了很多种办法,都不行,可能对它的Api还不够了解.所以用的时候需要考虑这点.使用ManualResetEvent信号量,主线程只能等待一个子线程的完成.

用法如下:

(1)、ManualResetEvent

        static  void Main(string[] args){ManualResetEvent mre = new ManualResetEvent(false);//创建ManualResetEvent信号量,主线这里构造函数必须传递falseThreadPool.QueueUserWorkItem((o => eowOne(mre)));//开启辅助线程mre.WaitOne();//让主线程等待子线程的完成Console.WriteLine("主线程继续做它的事情!");Console.Read();}/// <summary>/// 辅助线程一/// </summary>static void eowOne(ManualResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程一做完了它的事,耗时:{0}", watch.ElapsedMilliseconds/1000);mre.Set();//告诉主线程子线程执行完了,如果不给ManualResetEvent实例调用这个方法,主线程会一直等待子线程调用ManualResetEvent实例的Set方法}

如果子线程不调用Set方法,子线程代码如下:

        /// <summary>/// 辅助线程一/// </summary>static void eowOne(ManualResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程一做完了它的事,耗时:{0}", watch.ElapsedMilliseconds/1000);}

子线程做完了它的事情,但是没有调用ManualResetEvent实例的Set方法,所以,主线程会一直等待.这里主线程就被阻塞了.

结论:

(1)、当给ManualResetEvent实例的构造函数传false的时候,主线程调用ManualResetEvent实例的WaitOne方法时,如果子线程没有调用ManualResetEvent实例的Set方法,那么主线程会阻塞.

(2)、如果子线程调用了ManualResetEvent实例的Set方法,那么主线程调用ManualResetEvent实例的WaitOne方法,那么主线程会接收到一个子线程已经完成的信号,并且继续执行.不会阻塞.

(3)、无论怎么样主线程都会阻塞,只是不调用Set,主线程永远阻塞了,执行不下去了,调用Set,主线程还是会阻塞,但是当子线程完成工作之后,它会继续执行.

 

(2)、ManualResetEvent的ReSet方法

让ManualResetEvent实例回归初始状态

        static  void Main(string[] args){ManualResetEvent mre = new ManualResetEvent(false);//创建ManualResetEvent信号量,主线这里构造函数必须传递falseThreadPool.QueueUserWorkItem((o => eowOne(mre)));//开启辅助线程一mre.WaitOne();//让主线程等待辅助线程一的完成mre.Reset();//调用ReSet方法,让ManualResetEvent回到初始状态,如果不使用这个方法,主线程不会等待辅助线程二,直接执行,因为辅助线程一已经调用了mre.Set方法ThreadPool.QueueUserWorkItem((o => eowTwo(mre)));//开启辅助线程二mre.WaitOne();//让主线程等待子线程辅助线程二的完成Console.WriteLine("主线程继续做它的事情!");Console.Read();}/// <summary>/// 辅助线程一/// </summary>static void eowOne(ManualResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程一做完了它的事,耗时:{0}", watch.ElapsedMilliseconds/1000);mre.Set();}/// <summary>/// 辅助线程二/// </summary>static void eowTwo(ManualResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程二做完了它的事,耗时:{0}", watch.ElapsedMilliseconds / 1000);var status = mre.Set();if (status){mre.Reset();}}

ok,主线程会依次等待两个线程顺序执行完它们的事情,你可能发现一个问题.这和同步有什么区别!哈哈,有区别,如果主线程执行的任务足够耗时,而且执行到某一个时段,需要判断子线程是否完成,获取需要子线程的返回值(当然TreadPool不能很友好的拿到返回值),这个时候这种做法就有优势了两个线程各自承担自己的事情,互不干扰,需要协同操作了,主线程调用下Wait方法,确认子线程正确的完成了它的操作之后,继续执行主线程的任务..所以需谨慎使用.主线程如果啥都不干,光光去等待子线程完成,这种情况和同步就没有删么区别了.所以这个过程可能会卡界面.也有可能不卡.

 

(3)、AutoResetEvent信号量

AutoResetEvent和ManualResetEvent大体上没什么区别,都是阻塞主线程,但是ManualResetEvent需要每次调用ReSet方法而AutoResetEvent不用.

        static  void Main(string[] args){AutoResetEvent mre = new AutoResetEvent(false);//创建ManualResetEvent信号量,主线这里构造函数必须传递falseThreadPool.QueueUserWorkItem((o => eowOne(mre)));//开启辅助线程一mre.WaitOne();//让主线程等待辅助线程一的完成ThreadPool.QueueUserWorkItem((o => eowTwo(mre)));//开启辅助线程二mre.WaitOne();//让主线程等待子线程辅助线程二的完成Console.WriteLine("主线程继续做它的事情!");Console.Read();}/// <summary>/// 辅助线程一/// </summary>static void eowOne(AutoResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程一做完了它的事,耗时:{0}", watch.ElapsedMilliseconds/1000);mre.Set();}/// <summary>/// 辅助线程二/// </summary>static void eowTwo(AutoResetEvent mre){var watch = Stopwatch.StartNew();Thread.Sleep(2000);watch.Stop();Console.WriteLine("辅助线程二做完了它的事,耗时:{0}", watch.ElapsedMilliseconds / 1000);var status = mre.Set();if (status){mre.Reset();}}

 

 

转载于:https://www.cnblogs.com/GreenLeaves/p/9980979.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/364904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle学习:新建表空间

1. 以 sysdba 身份登入Oracle sqlplus / as sysdba; 2. 创建表空间 create tablespace (空间名)fwptfs (数据文件存放路径)datafile D:\xxx (初始大小)size 500m (自动扩容&#xff0c;每次200m)autoextend on next 200m; 3. 创建用户 create user (用户名)fwptfs…

tomcat7使用dbcp连接池遇到的坑

项目部署在tomcat后每隔一段时间便会报错 Cause: java.sql.SQLException: Could not retrieve transation read-only status server ; SQL []; Could not retrieve transation read-only status server; nested exception is java.sql.SQLException: Could not retrieve transa…

纯CSS实现3D照片墙

HTML部分&#xff1a; <body><div class"photo-wrap"> <!-- 舞台 --><div class"container"> <!-- 容器 --><div class"img">我是中心</div><div class"img img01"><img src&q…

Guava之RangeMap

在Guava官方API上面可以得知&#xff1a;RangeMap是一种集合类型( collection type)&#xff0c;它将不相交、且不为空的Range&#xff08;key&#xff09;映射给一个值&#xff08;Value&#xff09;。和RangeSet不一样&#xff0c;RangeMap不可以将相邻的区间合并&#xff0c…

CSS3新增的伪类选择器

伪类选择器的作用&#xff1a;对已有选择器做进一步的限制&#xff0c;对已有选择器能匹配的元素做进一步的过滤。CSS 3提供的伪类选择器主要分为以下三类&#xff1a; 结构性伪类选择器UI元素状态伪类选择器其他伪类选择器 1、结构性伪类选择器 Selector:root&#xff1a;匹…

签名SOAP消息–生成封装的XML签名

数字签名是使数字内容真实可信的一种广泛使用的机制。 通过为某些内容生成数字签名&#xff0c;我们可以让另一方能够验证该内容。 通过此验证&#xff0c;它可以保证在我们签名后不会更改。 通过这个示例&#xff0c;我将分享如何为SOAP信封生成签名。 但是&#xff0c;这当然…

2019 The 19th Zhejiang University Programming Contest

感想&#xff1a; 今天三个人的状态比昨天计院校赛的状态要好很多&#xff0c;然而三个人都慢热体质导致签到题wa了很多发。最后虽然跟大家题数一样(6题)&#xff0c;然而输在罚时。 只能说&#xff0c;水题还是刷得少&#xff0c;看到签到都没灵感实在不应该。 题目链接&#…

openvas安装和基本使用

OpenVAS是开放式漏洞评估系统&#xff0c;也可以说它是一个包含着相关工具的网络扫描器。OpenVAS是开放式漏洞评估系统&#xff0c;也可以说它是一个包含着相关工具的网络扫描器。其核心部件是一个服务器&#xff0c;包括一套网络漏洞测试程序&#xff0c;可以检测远程系统和应…

修改mysql编码方式centos_CentOS下修改mysql数据库编码为UTF-8(附mysql开启远程链接和开放3306端口)...

楼主在配置好linux云服务器的jdk,tomcat,mysql后&#xff0c;当要开始部署项目是&#xff0c;忽然意识到一个很严重的问题&#xff0c;那就是数据库的编码问题&#xff0c;自安装完成后并未修改数据库的额编码。。。。下面就来讲说linux下修改mysql的编码问题吧。。有一个问题网…

srtvlet filter

Filter&#xff0c;过滤器&#xff0c;顾名思义&#xff0c;即是对数据等的过滤&#xff0c;预处理过程。为什么要引入过滤器呢&#xff1f;在平常访问网站的时候&#xff0c;有时候发一些敏感的信息&#xff0c;发出后显示时 就会将敏感信息用*等字符替代&#xff0c;这就是用…

mysql怎么合并行_mysql怎么合并行

mysql合并行的方法&#xff1a;使用函数【GROUP_CONCAT()】&#xff0c;代码为【SELECT am.activeId,GROUP_CONCAT(m.modelName SEPARATOR ‘,’) modelName】。【相关学习推荐&#xff1a;mysql学习】mysql合并行的方法&#xff1a;一个字段可能对应多条数据&#xff0c;用mys…

将旧项目从Ant迁移到Maven的4个简单步骤

一段时间以来&#xff0c;我们一直在考虑将构建从蚂蚁移植到Maven。 它发生在上个月&#xff0c;实际上比我们预期的要简单。 根据我的经验&#xff0c;这里简要介绍了我们遵循的步骤。 我们的应用程序是一个具有多个框架和技术的企业Web应用程序构建&#xff0c;并作为单个WAR…

折腾Java设计模式之建造者模式

博文原址&#xff1a;折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, allowing the same construction process to create various representations. 将复杂对象的构造与其表现分离&#xff0c;允许相同的构…

python小甲鱼练习题答案_小甲鱼Python第 013讲元组:戴上了枷锁的列表 | 课后测试题及参考答案...

测试题&#xff1a;0. 请用一句话描述什么是列表&#xff1f;再用一句话描述什么是元组&#xff1f;列表&#xff1a;一个大仓库&#xff0c;可以随时往里面添加和删除任何东西。元祖&#xff1a;封闭的列表&#xff0c;一旦定义&#xff0c;就不可改变(不能添加、删除或修改)1…

Vue--- 一点车项目

一点车项目 cli脚手架 组件化 数据交互路由指向存入数据库 前端页面 cli脚手架的安装与搭建 创建对应包 页面组件化编辑 &#xff08;共享组件&#xff1a;摘取出来一模一样的组件重用&#xff09;&#xff08;私有组件:在自己的组件写入 引入共享组件&#xff09; 数据交…

设计模式:模式或反模式,这就是问题

我最近遇到了Wiki页面“ Anti-pattern” &#xff0c;其中包含详尽的反模式列表。 其中一些对我来说很明显。 他们中的一些让我想了一下&#xff0c;其他的让我想了更多。 然后&#xff0c;我开始在页面上查找反模式“ singleton”&#xff0c;但找不到。 &#xff08;文本搜索…

Redis的散列类型

Redis是采用字典结构以key-value的形式存储数据的&#xff0c;在散列类型&#xff08;所谓的hash&#xff09;中的value也是一种字典结构。如果用关系表结构去理解&#xff0c;就是key为对象&#xff0c;value是属性和属性值。如下图&#xff1a; 所以使用散列&#xff08;hash…

easy html css tree 简单的HTML css导航树

code: show: 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

Java实现并发线程中线程范围内共享数据

---恢复内容开始--- 利用Map&#xff0c;HashMap键值对的数据结构&#xff0c;实现并发线程中线程范围内数据共享。 package cn.qy.heima2;import java.util.HashMap; import java.util.Map; import java.util.Random;public class ThreadScopeShareData {private static int …

18.11.16-高等数学-曲率计算

11.16 转载于:https://www.cnblogs.com/coder211/p/10005502.html