最近用Timer踩了一个坑,分享一下避免别人继续踩
最近做一个小项目,项目中有一个定时服务,需要向对方定时发送数据,时间间隔是1.5s,然后就想到了用C#的Timer类,我们知道Timer
确实非常好用,因为里面有非常人性化的start和stop功能,在Timer里面还有一个Interval,就是用来设置时间间隔,然后时间间隔到了就会触
发Elapsed事件,我们只需要把callback函数注册到这个事件就可以了,如果Interval到了就会触发Elapsed,貌似一切看起来很顺其自然,但是
有一点一定要注意,callback函数本身执行也是需要时间的,也许这个时间是1s,2s或者更长时间,而timer类却不管这些,它只顾1.5s触发一下
Elapsed,这就导致了我的callback可能还没有执行完,下一个callback又开始执行了,也就导致了没有达到我预期的1.5s的效果,并且还出现了
一个非常严重的问题,那就是线程激增,非常恐怖。
下面举个例子,为了简化一下,我就定义一个task任务,当然项目中是多个task任务一起跑的。
一:问题产生
为了具有更高的灵活性,我定义了一个CustomTimer类继承自Timer,然后里面可以放些Task要跑的数据,这里就定义一个Queue。
namespace Sample class Program static void Main(string[] args) TimerCustom timer = new TimerCustom(); timer.Interval = 1500; timer.Elapsed += (obj, evt) = TimerCustom singleTimer = obj as TimerCustom; if (singleTimer != null) if (singleTimer.queue.Count != 0) var item = singleTimer.queue.Dequeue(); Send(item); timer.Start(); Console.Read(); static void Send(int obj) //随机暂定8-10s Thread.Sleep(new Random().Next(8000, 10000)); Console.WriteLine("当前时间:{0},定时数据发送成功!", DateTime.Now); class TimerCustom : System.Timers.Timer public Queue int queue = new Queue int public TimerCustom() for (int i = 0; i short.MaxValue; i++) queue.Enqueue(i); }
二:解决方法
1. 从上图看,在一个任务的情况下就已经有14个线程了,并且在21s的时候有两个线程同时执行了,我的第一反应就是想怎么把后续执行callback的
线程踢出去,也就是保证当前仅让两个线程在用callback,一个在执行,一个在等待执行,如果第一个线程的callback没有执行完,后续如果来了第三
个线程的话,我就把这第三个线程直接踢出去,直到第一个callback执行完后,才允许第三个线程进来并等待执行callback,然后曾今的第二个线程开
始执行callback,后续的就以此类推。。。
然后我就想到了用lock机制,在customTimer中增加lockMe,lockNum,isFirst字段,用lockMe来锁住,用lockNum来踢当前多余的要执行callback
的线程,用isFirst来判断是不是第一次执行该callback,后续callback的线程必须先等待1.5s再执行。
namespace Sample class Program static void Main(string[] args) TimerCustom timer = new TimerCustom(); timer.Interval = 1500; timer.Elapsed += (obj, evt) = TimerCustom singleTimer = obj as TimerCustom; if (singleTimer != null) //如果当前等待线程 2,就踢掉该线程 if (Interlocked.Read(ref singleTimer.lockNum) 2) return; Interlocked.Increment(ref singleTimer.lockNum); //这里的lock只能存在一个线程等待 lock (singleTimer.lockMe) if (!singleTimer.isFirst) Thread.Sleep((int)singleTimer.Interval); singleTimer.isFirst = false; if (singleTimer.queue.Count != 0) var item = singleTimer.queue.Dequeue(); Send(item); Interlocked.Decrement(ref singleTimer.lockNum); timer.Start(); Console.Read(); static void Send(int obj) Thread.Sleep(new Random().Next(8000, 10000)); Console.WriteLine("当前时间:{0},邮件发送成功!", DateTime.Now); class TimerCustom : System.Timers.Timer public Queue int queue = new Queue int public object lockMe = new object(); public bool isFirst = true; /// summary /// 为保持连贯性,默认锁住两个 /// /summary public long lockNum = 0; public TimerCustom() for (int i = 0; i short.MaxValue; i++) queue.Enqueue(i); }
从图中可以看到,已经没有同一秒出现重复任务的发送情况了,并且线程也给压制下去了,乍一看效果不是很明显,不过这是在一个任务的情况
下的场景,任务越多就越明显了,所以这个就达到我要的效果。
2. 从上面的解决方案来看,其实我们的思维已经被问题约束住了,当时我也是这样,毕竟坑出来了,就必须来填坑,既然在callback中出现线程
蜂拥的情况,我当然要想办法管制了,其实这也没什么错,等问题解决了再回头考虑下时,我们会发现文章开头说的Timer类有强大的Stop和
Start功能,所以。。。。这个时候思维就跳出来了,何不在callback执行的时候把Timer关掉,执行完callback后再把Timer开启,这样不就
可以解决问题吗?好吧,说干就干。
namespace Sample class Program static void Main(string[] args) TimerCustom timer = new TimerCustom(); timer.Interval = 1500; timer.Elapsed += (obj, evt) = TimerCustom singleTimer = obj as TimerCustom; //先停掉 singleTimer.Stop(); if (singleTimer != null) if (singleTimer.queue.Count != 0) var item = singleTimer.queue.Dequeue(); Send(item); //发送完成之后再开启 singleTimer.Start(); timer.Start(); Console.Read(); static void Send(int obj) Thread.Sleep(new Random().Next(8000, 10000)); Console.WriteLine("当前时间:{0},邮件发送成功!", DateTime.Now); class TimerCustom : System.Timers.Timer public Queue int queue = new Queue int public object lockMe = new object(); /// summary /// 为保持连贯性,默认锁住两个 /// /summary public long lockNum = 0; public TimerCustom() for (int i = 0; i short.MaxValue; i++) queue.Enqueue(i); }
从图中可以看到,问题同样得到解决,而且更简单,精妙。
最后总结一下:解决问题的思维很重要,但是如果跳出思维站到更高的抽象层次上考虑问题貌似也很难得。。。
钟同学,this is for you! 坐在我旁边的钟同学听说我精通Mybatis源码(我就想不通,是谁透漏了风声),就顺带问了我一个问题:在同一个方法中,Mybatis多次请求数据库,是否要创建多个SqlSession会话?
【建议收藏】90%的前端都会踩的坑,你中了吗?(上) 移动开发时代,前端同学刚刚送别了让人头秃的IE浏览器,却发现憧憬已久的移动互联网时代并不是想象中那般美好。各种棘手的系统兼容问题和浏览器兼容问题怎么也让人高兴不起来。作为一名工作不足3年的前端程序媛,始终相信好记性不如烂笔头。每次在项目开发过程中踩到的坑,都习惯性地记录了下来。昨日一瞥竟积少成多,稍感诧异。因此分享出来,希望对大家能有所帮助。
【建议收藏】90%的前端都会踩的坑,你中了吗?(下) 移动开发时代,前端同学刚刚送别了让人头秃的IE浏览器,却发现憧憬已久的移动互联网时代并不是想象中那般美好。各种棘手的系统兼容问题和浏览器兼容问题怎么也让人高兴不起来。作为一名工作不足3年的前端程序媛,始终相信好记性不如烂笔头。每次在项目开发过程中踩到的坑,都习惯性地记录了下来。昨日一瞥竟积少成多,稍感诧异。因此分享出来,希望对大家能有所帮助。
【建议收藏】90%的前端都会踩的坑,你中了吗?(中) 移动开发时代,前端同学刚刚送别了让人头秃的IE浏览器,却发现憧憬已久的移动互联网时代并不是想象中那般美好。各种棘手的系统兼容问题和浏览器兼容问题怎么也让人高兴不起来。作为一名工作不足3年的前端程序媛,始终相信好记性不如烂笔头。每次在项目开发过程中踩到的坑,都习惯性地记录了下来。昨日一瞥竟积少成多,稍感诧异。因此分享出来,希望对大家能有所帮助。
Jenkin踩过的坑~上 以前的版本,安装成windwos服务的话,所有的文件都会在安装目录下 ,最近下了个2.253版本在电脑上进行安装的时候,发现安装后,在安装目录下只有少量的几个文件和一个war包,其他的插件目录和其他的一些文件夹的目录,都会写入到以下目录下去了。
相关文章
- 【华为云技术分享】玩转小熊派BearPi(一)使用STM32CubeMX + HAL点亮一个LED
- 【华为云技术分享】如何做一个优秀软件-可扩展的架构,良好的编码,可信的过程
- 3分钟创建一个游戏类容器应用【华为云分享】
- 分享一个高清壁纸网站
- 一个 15 年 SAP ABAP 开发人员分享的 SAPGUI 一些个性化设置和实用小技巧
- ABAP面试题:如何翻转一个ABAP内表
- 一个 Angular 程序员两年多的远程办公经验分享
- 【反传销】春节一个短暂误入传销和脱身的真实故事以及对技术的思考(三)回忆、传销特征与解救
- 用Python做一个游戏辅助脚本,完整编程思路分享!
- 一个人开发的html整站源码分享网站就这么上线了
- 一位上了一个大的互联网公司笔试题分享
- 一个free的问题
- 【数字IC/FPGA】检测最后一个匹配序列的位置
- petshop4.0 具体解释之中的一个(系统架构设计)
- 我有一个梦想--3G时代
- 这是多年软件测试大牛分享成长经历,一个好的软件测试工程师应该做到这些!
- tflearn 在每一个epoch完毕保存模型
- Helm 从入门到实践 | 从 0 开始制作一个 Helm Charts