最近用Timer踩了一个坑,分享一下避免别人继续踩

2024-08-29 23:32

本文主要是介绍最近用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,isFirst字段,用lockMe来锁住,用lockNum来踢当前多余的要执行callback

的线程,用isFirst来判断是不是第一次执行该callback,后续callback的线程必须先等待1.5s再执行。

复制代码
 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.isFirst)
27                         {
28                             Thread.Sleep((int)singleTimer.Interval);
29                         }
30 
31                         singleTimer.isFirst = false;
32 
33                         if (singleTimer.queue.Count != 0)
34                         {
35                             var item = singleTimer.queue.Dequeue();
36 
37                             Send(item);
38 
39                             Interlocked.Decrement(ref singleTimer.lockNum);
40                         }
41                     }
42                 }
43             };
44 
45             timer.Start();
46 
47             Console.Read();
48         }
49 
50         static void Send(int obj)
51         {
52             Thread.Sleep(new Random().Next(8000, 10000));
53 
54             Console.WriteLine("当前时间:{0},邮件发送成功!", DateTime.Now);
55         }
56     }
57 
58     class TimerCustom : System.Timers.Timer
59     {
60         public Queue<int> queue = new Queue<int>();
61 
62         public object lockMe = new object();
63 
64         public bool isFirst = true;
65 
66         /// <summary>
67         /// 为保持连贯性,默认锁住两个
68         /// </summary>
69         public long lockNum = 0;
70 
71         public TimerCustom()
72         {
73             for (int i = 0; i < short.MaxValue; i++)
74             {
75                 queue.Enqueue(i);
76             }
77         }
78     }
79 }
复制代码

 

 

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

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

 

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 }
复制代码

 

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


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

这篇关于最近用Timer踩了一个坑,分享一下避免别人继续踩的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1119158

相关文章

Python处理大量Excel文件的十个技巧分享

《Python处理大量Excel文件的十个技巧分享》每天被大量Excel文件折磨的你看过来!这是一份Python程序员整理的实用技巧,不说废话,直接上干货,文章通过代码示例讲解的非常详细,需要的朋友可... 目录一、批量读取多个Excel文件二、选择性读取工作表和列三、自动调整格式和样式四、智能数据清洗五、

JDK9到JDK21中值得掌握的29个实用特性分享

《JDK9到JDK21中值得掌握的29个实用特性分享》Java的演进节奏从JDK9开始显著加快,每半年一个新版本的发布节奏为Java带来了大量的新特性,本文整理了29个JDK9到JDK21中值得掌握的... 目录JDK 9 模块化与API增强1. 集合工厂方法:一行代码创建不可变集合2. 私有接口方法:接口

电脑系统Hosts文件原理和应用分享

《电脑系统Hosts文件原理和应用分享》Hosts是一个没有扩展名的系统文件,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应... Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应

使用@Cacheable注解Redis时Redis宕机或其他原因连不上继续调用原方法的解决方案

《使用@Cacheable注解Redis时Redis宕机或其他原因连不上继续调用原方法的解决方案》在SpringBoot应用中,我们经常使用​​@Cacheable​​注解来缓存数据,以提高应用的性能... 目录@Cacheable注解Redis时,Redis宕机或其他原因连不上,继续调用原方法的解决方案1

正则表达式r前缀使用指南及如何避免常见错误

《正则表达式r前缀使用指南及如何避免常见错误》正则表达式是处理字符串的强大工具,但它常常伴随着转义字符的复杂性,本文将简洁地讲解r的作用、基本原理,以及如何在实际代码中避免常见错误,感兴趣的朋友一... 目录1. 字符串的双重翻译困境2. 为什么需要 r?3. 常见错误和正确用法4. Unicode 转换的

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Python解析器安装指南分享(Mac/Windows/Linux)

《Python解析器安装指南分享(Mac/Windows/Linux)》:本文主要介绍Python解析器安装指南(Mac/Windows/Linux),具有很好的参考价值,希望对大家有所帮助,如有... 目NMNkN录1js. 安装包下载1.1 python 下载官网2.核心安装方式3. MACOS 系统安

Java嵌套for循环优化方案分享

《Java嵌套for循环优化方案分享》介绍了Java中嵌套for循环的优化方法,包括减少循环次数、合并循环、使用更高效的数据结构、并行处理、预处理和缓存、算法优化、尽量减少对象创建以及本地变量优化,通... 目录Java 嵌套 for 循环优化方案1. 减少循环次数2. 合并循环3. 使用更高效的数据结构4