最近做一个小项目,项目中有一个定时服务,需要向对方定时发送数据,时间间隔是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踩了一个坑,分享一下避免别人继续踩的更多相关文章

  1. 【转】最近用Timer踩了一个坑,分享一下避免别人继续踩

    [转]最近用Timer踩了一个坑,分享一下避免别人继续踩 最近做一个小项目,项目中有一个定时服务,需要向对方定时发送数据,时间间隔是1.5s,然后就想到了用C#的Timer类,我们知道Timer 确实 ...

  2. 使用ffmpeg视频编码过程中踩的一个坑

           今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,非常多时候一旦思维定势真的挺难突破的.以下是不对的编码结果:                   ...

  3. 记新人从 excel 文件中读取字典数据踩的一个坑

    原本是打算今天分享一下最近学习接口自动化的心得体会,然而在我写模板的时候,却被一个坑拦我大半天,心得体会不得不 延期再分享了.在我无数次调试无数次看log,终于发现并解决这个问题了.下面记录一下踩的坑 ...

  4. toFixed()一不小心踩了一个坑

    toFixed,多么简单的一个函数,昨天突发奇想做两道算法题练练手.结果,踩到了一个从未遇到的坑! \n 简单来讲是要对输入的很多组数据,自己写一个函数做个处理,把每次函数处理的结果要相加求和.最后输 ...

  5. Javascript之旅——第八站:说说instanceof踩了一个坑

    前些天写js遇到了一个instanceof的坑,我们的页面中有一个iframe,我在index页面中计算得到了一个array,然后需要传递到Flight页面 这个嵌套的iframe中的一个函数(Sea ...

  6. java 反射的踩的一个坑

    今天工作的时候用到了一个反射.其业务简单描述为:系统启动时将需要定时调用的方法签名保存到数据库中,开启线程定时从数据库中读取对应的方法签名,通过反射生成实例后调用方法.完成一定的定时任务. 写到的方法 ...

  7. mongodb 的Cursor 作为 stream 的时候,读出来的数据数字开头的key没法访问(又踩了一个坑)

    mongdb 用Cursor 读取数据的时候,直接用流读出来的数据key是数字开头的话,就是独具不到,用Object.keys() 把所有的key 打印出来的话如下:怎么会是这样的呢? 查看了一下文档 ...

  8. 看了下opengl相关的资料,踩了一个坑,记录一下

    2019/03/10 下午看了下关于opengl的资料,然后把敲了下代码,然后程序报错了.代码如下: #include <glad/glad.h> #include <GLFW/gl ...

  9. leaflet control.layers踩的一个坑

    Control.Layers方法 该方法可以创建一个切换图层的工具, L.control.layers(baseLayers, overlayers).addTo(map); baseLayers参数 ...

随机推荐

  1. 【Java每日一题】20161130

    20161129问题解析请点击今日问题下方的"[Java每日一题]20161130"查看 package Nov2016; public class Ques1130 { publ ...

  2. Scalaz(17)- Monad:泛函状态类型-State Monad

    我们经常提到函数式编程就是F[T].这个F可以被视为一种运算模式.我们是在F运算模式的壳子内对T进行计算.理论上来讲,函数式程序的运行状态也应该是在这个运算模式壳子内的,也是在F[]内更新的.那么我们 ...

  3. [PHP] 使用Socket提供Http服务

    我的SimpleLoader里面的一块 https://github.com/taoshihan1991/simpleloader <?php namespace Server; class S ...

  4. Java--简单的Spring AOP配置以及AOP事物管理,JDK/GCLib动态代理

    一.看一下简单的通过XML的AOP配置 1.首先创建一个简单的Student类 public class Student { private Integer age; private String n ...

  5. HDU 3328 Flipper 栈 模拟

    首先想说,英语太烂这题读了很长时间才读懂......题意是说输入有几张牌,然后输入这些牌的初始状态(是面朝上还是面朝下),然后输入操作方式,R表示翻一下右边的牌堆,L表示翻一下左边的牌堆,直到最后摞成 ...

  6. python 实时遍历日志文件

    首先尝试使用 python open 遍历一个大日志文件, 使用 readlines() 还是 readline() ? 总体上 readlines() 不慢于python 一次次调用 readlin ...

  7. 对chain.doFilter(request,response)的理解

    他的作用是将请求转发给过滤器链上下一个对象.这里的“下”指的是哪里 ? 指的是下一个filter,如果没有filter那就是你请求的资源. 一般filter都是一个链,web.xml 里面配置了几个就 ...

  8. 小白的CSS基础学习

    CSS定义: CSS全称为“层叠样式表 (Cascading Style Sheets)”,它主要是用于定义HTML内容在浏览器内的显示样式. CSS代码语法: css样式选择组成部分:选择符+声明( ...

  9. Extjs 使用fileupload插件上传文件 带进度条显示

    一.首先我们看看官方给出的插件的解释: 一个文件上传表单项具有自定义的样式,并且可以控制按钮的文本和 像文本表单的空文本类似的其他特性. 它使用一个隐藏的文件输入元素,并在用户选择文件后 在form提 ...

  10. Gartner:用自适应安全架构来应对高级定向攻击

    发表于2015-06-24   摘要:当前的防护功能难以应对高级的定向攻击,由于企业系统所受到的是持续攻击,并持续缺乏防御力,面向“应急响应”的特别方式已不再是正确的思维模式,Garnter提出了用自 ...