【转】最近用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 = ; timer.Elapsed += (obj, evt) =>
{
TimerCustom singleTimer = obj as TimerCustom; if (singleTimer != null)
{
if (singleTimer.queue.Count != )
{
var item = singleTimer.queue.Dequeue(); Send(item);
}
}
}; timer.Start(); Console.Read();
} static void Send(int obj)
{
//随机暂定8-10s
Thread.Sleep(new Random().Next(, )); Console.WriteLine("当前时间:{0},定时数据发送成功!", DateTime.Now);
}
} class TimerCustom : System.Timers.Timer
{
public Queue<int> queue = new Queue<int>(); public TimerCustom()
{
for (int i = ; 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 = ; timer.Elapsed += (obj, evt) =>
{
TimerCustom singleTimer = obj as TimerCustom; if (singleTimer != null)
{
//如果当前等待线程>2,就踢掉该线程
if (Interlocked.Read(ref singleTimer.lockNum) > )
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 != )
{
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(, )); 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 = ; public TimerCustom()
{
for (int i = ; 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 = ; timer.Elapsed += (obj, evt) =>
{
TimerCustom singleTimer = obj as TimerCustom; //先停掉
singleTimer.Stop(); if (singleTimer != null)
{
if (singleTimer.queue.Count != )
{
var item = singleTimer.queue.Dequeue(); Send(item); //发送完成之后再开启
singleTimer.Start();
}
}
}; timer.Start(); Console.Read();
} static void Send(int obj)
{
Thread.Sleep(new Random().Next(, )); 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 = ; public TimerCustom()
{
for (int i = ; i < short.MaxValue; i++)
{
queue.Enqueue(i);
}
}
}
}

从图中可以看到,问题同样得到解决,而且更简单,精妙。
最后总结一下:解决问题的思维很重要,但是如果跳出思维站到更高的抽象层次上考虑问题貌似也很难得。。。
【转】最近用Timer踩了一个坑,分享一下避免别人继续踩的更多相关文章
- 使用ffmpeg视频编码过程中踩的一个坑
今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,非常多时候一旦思维定势真的挺难突破的.以下是不对的编码结果: ...
- 最近用Timer踩了一个坑,分享一下避免别人继续踩
最近做一个小项目,项目中有一个定时服务,需要向对方定时发送数据,时间间隔是1.5s,然后就想到了用C#的Timer类,我们知道Timer 确实非常好用,因为里面有非常人性化的start和stop功能, ...
- 记新人从 excel 文件中读取字典数据踩的一个坑
原本是打算今天分享一下最近学习接口自动化的心得体会,然而在我写模板的时候,却被一个坑拦我大半天,心得体会不得不 延期再分享了.在我无数次调试无数次看log,终于发现并解决这个问题了.下面记录一下踩的坑 ...
- toFixed()一不小心踩了一个坑
toFixed,多么简单的一个函数,昨天突发奇想做两道算法题练练手.结果,踩到了一个从未遇到的坑! \n 简单来讲是要对输入的很多组数据,自己写一个函数做个处理,把每次函数处理的结果要相加求和.最后输 ...
- Javascript之旅——第八站:说说instanceof踩了一个坑
前些天写js遇到了一个instanceof的坑,我们的页面中有一个iframe,我在index页面中计算得到了一个array,然后需要传递到Flight页面 这个嵌套的iframe中的一个函数(Sea ...
- java 反射的踩的一个坑
今天工作的时候用到了一个反射.其业务简单描述为:系统启动时将需要定时调用的方法签名保存到数据库中,开启线程定时从数据库中读取对应的方法签名,通过反射生成实例后调用方法.完成一定的定时任务. 写到的方法 ...
- mongodb 的Cursor 作为 stream 的时候,读出来的数据数字开头的key没法访问(又踩了一个坑)
mongdb 用Cursor 读取数据的时候,直接用流读出来的数据key是数字开头的话,就是独具不到,用Object.keys() 把所有的key 打印出来的话如下:怎么会是这样的呢? 查看了一下文档 ...
- 看了下opengl相关的资料,踩了一个坑,记录一下
2019/03/10 下午看了下关于opengl的资料,然后把敲了下代码,然后程序报错了.代码如下: #include <glad/glad.h> #include <GLFW/gl ...
- leaflet control.layers踩的一个坑
Control.Layers方法 该方法可以创建一个切换图层的工具, L.control.layers(baseLayers, overlayers).addTo(map); baseLayers参数 ...
随机推荐
- Ext.NET Grid Group分组使用
- 需要注意的是, 涉及到分页排序, 最好定义GroupDir 方向与分组方式相同. - 譬如工资表按照最新最前分页输出. 如果分组按照默认排序的话, 最就最前. - 界面呈现出2015年, 2016 ...
- gitea 源码阅读笔记 002 生成无依赖单文件可执行包
gitea bindata static gitea 可以通过 make generate 生成一个单文件可执行程序, 该文件不需要任何其它依赖,直接可以单独执行. 对于用户的安装.升级和生成dock ...
- nodejs模块循环引用讲解
CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行.一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出. 让我 ...
- java 错误
ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2 解决在程序最后加一条语句system. ...
- android 开发 View _15 导入一张图片将它裁剪成圆形 与 paint图层叠加处理详解
方法一: /* 实现思维是这样的: 1.首先拿到bitmap图片 2.得到bitmap图片的高度 宽度,并且计算好各个画图尺寸 3.创建一个空白的 bitmap图片: Bitmap output = ...
- Maven下CXF的wsdl2java生成客户端代码
<plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin< ...
- jquery.cookie用法及其注意点
jquery.cookie是一个轻量级的cookie插件,由于已被封装好,可拿来即用. 基本的创建.读取.删除见另一篇文章 浅谈localStorage.sessionStorage 与cookie ...
- Python第1天
今天主要学习内容如下: 概论,各种开发语言的对比,高级语言包括:python(开发效率高,执行效率低) Java(开发效率低,执行效率高),PHP,低级语言包括:C语言,汇编语言: Python 语言 ...
- jqGrid基本用法与示例
转自:https://chuanlu.iteye.com/blog/1953544 一.jqGrid的基本用法 1.html页面 <!DOCTYPE html PUBLIC "-//W ...
- 多个css样式合并到一个“目录”css文件中
执行访问jsp后发现没有效果 同样的代码,在html中效果对比如下: 具体原因:不清楚,暂时记着~~~在jsp中不支持@import这种css样式的引用