最近用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。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8
 9             timer.Interval = 1500;
10
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14
15                 if (singleTimer != null)
16                 {
17                     if (singleTimer.queue.Count != 0)
18                     {
19                         var item = singleTimer.queue.Dequeue();
20
21                         Send(item);
22                     }
23                 }
24             };
25
26             timer.Start();
27
28             Console.Read();
29         }
30
31         static void Send(int obj)
32         {
33             //随机暂定8-10s
34             Thread.Sleep(new Random().Next(8000, 10000));
35
36             Console.WriteLine("当前时间:{0},定时数据发送成功!", DateTime.Now);
37         }
38     }
39
40     class TimerCustom : System.Timers.Timer
41     {
42         public Queue<int> queue = new Queue<int>();
43
44         public TimerCustom()
45         {
46             for (int i = 0; i < short.MaxValue; i++)
47             {
48                 queue.Enqueue(i);
49             }
50         }
51     }
52 }

二:解决方法

1.  从上图看,在一个任务的情况下就已经有14个线程了,并且在21s的时候有两个线程同时执行了,我的第一反应就是想怎么把后续执行callback的

线程踢出去,也就是保证当前仅让两个线程在用callback,一个在执行,一个在等待执行,如果第一个线程的callback没有执行完,后续如果来了第三

个线程的话,我就把这第三个线程直接踢出去,直到第一个callback执行完后,才允许第三个线程进来并等待执行callback,然后曾今的第二个线程开

始执行callback,后续的就以此类推。。。

然后我就想到了用lock机制,在customTimer中增加lockMe,lockNum等字段,用lockMe来锁住,用lockNum来踢当前多余的要执行callback的线程。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8
 9             timer.Interval = 1500;
10
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14
15                 if (singleTimer != null)
16                 {
17                     //如果当前等待线程>2,就踢掉该线程
18                     if (Interlocked.Read(ref singleTimer.lockNum) > 2)
19                         return;
20
21                     Interlocked.Increment(ref singleTimer.lockNum);
22
23                     //这里的lock只能存在一个线程等待
24                     lock (singleTimer.lockMe)
25                     {
26                         if (singleTimer.queue.Count != 0)
27                         {
28                             var item = singleTimer.queue.Dequeue();
29
30                             Send(item);
31
32                             Interlocked.Decrement(ref singleTimer.lockNum);
33                         }
34                     }
35                 }
36             };
37
38             timer.Start();
39
40             Console.Read();
41         }
42
43         static void Send(int obj)
44         {
45             Thread.Sleep(new Random().Next(8000, 10000));
46
47             Console.WriteLine("当前时间:{0},邮件发送成功!", DateTime.Now);
48         }
49     }
50
51     class TimerCustom : System.Timers.Timer
52     {
53         public Queue<int> queue = new Queue<int>();
54
55         public object lockMe = new object();
56
57         /// <summary>
58         /// 为保持连贯性,默认锁住两个
59         /// </summary>
60         public long lockNum = 0;
61
62         public TimerCustom()
63         {
64             for (int i = 0; i < short.MaxValue; i++)
65             {
66                 queue.Enqueue(i);
67             }
68         }
69     }
70 }

从图中可以看到,已经没有同一秒出现重复任务的发送情况了,并且线程也给压制下去了,乍一看效果不是很明显,不过这是在一个任务的情况

下的场景,任务越多就越明显了,所以这个就达到我要的效果。

2. 从上面的解决方案来看,其实我们的思维已经被问题约束住了,当时我也是这样,毕竟坑出来了,就必须来填坑,既然在callback中出现线程

  蜂拥的情况,我当然要想办法管制了,其实这也没什么错,等问题解决了再回头考虑下时,我们会发现文章开头说的Timer类有强大的Stop和

Start功能,所以。。。。这个时候思维就跳出来了,何不在callback执行的时候把Timer关掉,执行完callback后再把Timer开启,这样不就

可以解决问题吗?好吧,说干就干。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8
 9             timer.Interval = 1500;
10
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14
15                 //先停掉
16                 singleTimer.Stop();
17
18                 if (singleTimer != null)
19                 {
20                     if (singleTimer.queue.Count != 0)
21                     {
22                         var item = singleTimer.queue.Dequeue();
23
24                         Send(item);
25
26                         //发送完成之后再开启
27                         singleTimer.Start();
28                     }
29                 }
30             };
31
32             timer.Start();
33
34             Console.Read();
35         }
36
37         static void Send(int obj)
38         {
39             Thread.Sleep(new Random().Next(8000, 10000));
40
41             Console.WriteLine("当前时间:{0},邮件发送成功!", DateTime.Now);
42         }
43     }
44
45     class TimerCustom : System.Timers.Timer
46     {
47         public Queue<int> queue = new Queue<int>();
48
49         public object lockMe = new object();
50
51         /// <summary>
52         /// 为保持连贯性,默认锁住两个
53         /// </summary>
54         public long lockNum = 0;
55
56         public TimerCustom()
57         {
58             for (int i = 0; i < short.MaxValue; i++)
59             {
60                 queue.Enqueue(i);
61             }
62         }
63     }
64 }

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

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

时间: 2024-10-09 10:11:22

最近用Timer踩了一个坑,分享一下避免别人继续踩的相关文章

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

今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,很多时候一旦思维定势真的挺难突破的.下面是不正确的编码结果: 使用ffmpeg做视频编码过程中,首先要新建数据帧,并为数据帧分配相应内存,以便于保存图像数据,为数据帧分配内存需要用到av_image_alloc()这个函数,该函数将根据传入的图像宽.高.图像格式.数据对齐基数等参数进行内存分配. 这其中有一个参数可能会让人迷惑,那就是数据对齐基数这个参数该设置多少?顺便说说为什么要数据对齐,之所以要对齐,主要

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

前些天写js遇到了一个instanceof的坑,我们的页面中有一个iframe,我在index页面中计算得到了一个array,然后需要传递到Flight页面 这个嵌套的iframe中的一个函数(SearchFlight)中,作为防御性编程,我需要在SearchFlight函数中进行参数检测,也就是判断过来的参数一 定是Array类型. 一:抛出问题 举个例子,下面有两个页面. Index.html页面 1 <!DOCTYPE html> 2 <html xmlns="http:

java 反射的踩的一个坑

今天工作的时候用到了一个反射.其业务简单描述为:系统启动时将需要定时调用的方法签名保存到数据库中,开启线程定时从数据库中读取对应的方法签名,通过反射生成实例后调用方法.完成一定的定时任务. 写到的方法签名如下 public Class A{ public void  statistics( Member member, boolean flag )   // 这么写是错的. } 调用  A.class().getMethod("statistics", Member.class, Bo

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

mongdb 用Cursor 读取数据的时候,直接用流读出来的数据key是数字开头的话,就是独具不到,用Object.keys() 把所有的key 打印出来的话如下:怎么会是这样的呢? 查看了一下文档,这个cursor返回的是一个object 模型,意味着他触发了真实的document 的实例,所以返回的是所有的documnet的实例(包含了其中的一些方法),所以可以用一些stream 的方法转化一下,真实的数据其实在_doc里面,所以可以用JSON.stringly()来转化一下.最简单的方法

安装openstack踩的一个坑

Openstack Mitaka在图形化界面的时候出现以下报错: [[email protected] ~]# openstack server create --flavor m1.tiny --image cirros --nic net-id=2d79dc77-be66-4850-af01-8e6946f16035 --security-group default --key-name mykey provider-instance Unexpected API Error. Please

使用SpringBoot的yml文件配置时踩的一个坑

问题描述:使用SpringBoot整合redis进行yml配置的时候,启动工程报错,提示加载application.yml配置文件失败: 17:18:27.430 [main] ERROR org.springframework.boot.SpringApplication - Application startup failed java.lang.IllegalStateException: Failed to load property source from location 'clas

Entity Framework的一个坑

由于业务需要写了一个批量数据导入工具.中间踩了一个坑 问: 1. SaveChange 实体A 发生pk冲突,异常了.2.记录日志3.不让退出程序,继续处理下一个实体4.Add新的实体B5.再次调用SaveChange6.问,这次SaveChange ,EF是保存实体A,还是实体B? 根据目前的结果看,EF还是会尝试保存实体A. 根本原因是实体A的状态还是Added. 必须在异常处理中,把实体A的状态修改为Detached ,才能摆脱异常魔咒. 原文地址:https://www.cnblogs.

iOS开发之记一次App卡顿Bug的解决历程(踩了一个StoryBoard的坑)

虽然今天是周末,但是还是要学习的不是.写这篇博客的目的呢是记录一下自己在上次项目迭代中踩的坑,不过这个坑已经填上了.虽然坑不大,但是踩上去肯定能崴脚.其实还是那句话,在没人给你指路的情况下,踩的坑多了,慢慢的就成长了.为了填今天要讲的这个坑,午觉都没睡呢.当然今天博客的内容并不高深,而且出现的几率还是蛮大的,所以喽就记录一下.也许你已经踩过,或者你已经将此坑填上,但是今天是我踩了一脚呢,没办法,还是记录一下吧. 解决历程用一个字描述就是:“删”. 一.描述这个“坑” 首先呢,我们先来看一下这个B

踩到Framework7 Photo Browser 的一个坑

最近在做的项目用了Framework7前端框架,功能确实比较强大!但这两天遇到一个坑,希望我的这点收获能给遇到这个问题的朋友一点帮助. 在使用Photo Browser 的时候,图片下方想放一个“点赞”的按钮,耐何就死活无法响应鼠标的点击事件(click tap都不行).怀疑被父级元素拦截了,反复各种折腾就是没效果! 最后都要放弃的时候,都准备移除“点赞”功能了,无意中发现.photo-browser-captions这个层有个样式是 pointer-events: none; 翻了一下CSS手