博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最近用Timer踩了一个坑,分享一下避免别人继续踩
阅读量:6704 次
发布时间:2019-06-25

本文共 5714 字,大约阅读时间需要 19 分钟。

  最近做一个小项目,项目中有一个定时服务,需要向对方定时发送数据,时间间隔是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
queue = new Queue
(); 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
queue = new Queue
(); public object lockMe = new object(); public bool isFirst = true; ///
/// 为保持连贯性,默认锁住两个 /// 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
queue = new Queue
(); public object lockMe = new object(); ///
/// 为保持连贯性,默认锁住两个 /// public long lockNum = 0; public TimerCustom() { for (int i = 0; i < short.MaxValue; i++) { queue.Enqueue(i); } } }}

 

从图中可以看到,问题同样得到解决,而且更简单,精妙。

最后总结一下:解决问题的思维很重要,但是如果跳出思维站到更高的抽象层次上考虑问题貌似也很难得。。。

转载地址:http://wbdlo.baihongyu.com/

你可能感兴趣的文章
获取断面线的范围
查看>>
WIFI: N, Legacy and AC
查看>>
SQLServer2012 (非)聚集索引存储探究
查看>>
Android Studio Share Project On Github
查看>>
数据库表的约束
查看>>
这个时代会残酷惩罚不肯改变的人
查看>>
HDU 1253 胜利大逃亡 NYOJ 523【BFS】
查看>>
UNITY中有Timer
查看>>
VIM 插入
查看>>
iOS 开发如何入门
查看>>
怎样使用ListView实现一个带有网络请求,解析,分页,缓存的公共的List页面来大大的提高工作效率...
查看>>
自然语言交流系统 phxnet团队 创新实训 个人博客 (十四)
查看>>
jQuery之异步Ajax请求使用
查看>>
原码 反码 补码 位运算
查看>>
关于Unity中的Input输入事件
查看>>
基于FFMPEG SDK流媒体开发1---解码媒体文件流信息
查看>>
【C/C++学院】0724-堆栈简单介绍/静态区/内存完毕篇/多线程
查看>>
Gson转Map
查看>>
(转)Openlayers 2.X加载高德地图
查看>>
牛顿迭代法及最小二乘法
查看>>