当前位置: 首页 > news >正文

Timer 控件中的Elapsed事件与tick事件的区别

public System.Timers.Timer Timer1 = new System.Timers.Timer(); 
elsapsed事件是在另一个线程中引发的 。如果 elapsed事件中的代码是耗时的代码
当事件引发后,调用Timer1.Stop() 方法, 但elapsed 事件线程中的代码还在执行,直到该次elapse事件中的代码全执行完。

 

Timer 控件没有Elapsed事件吧,System.Timers类才有。

Visual Studio 和 .NET Framework 中包含三个计时器控件:

可添加到“工具箱”中的基于服务器的计时器

始终位于“工具箱”中的基于 Windows 的计时器

可通过编程方式使用的线程计时器

基于 Windows 的计时器针对在 Windows 窗体应用程序中使用而进行了优化。基于服务器的计时器是传统的计时器为了在服务器环境上运行而优化后的更新版本。线程计时器是一种简单的、轻量级计时器,它使用回调方法而不是使用事件,并由线程池线程提供支持。

在 Win32 体系结构中有两种类型的线程:UI 线程和辅助线程。UI 线程绝大多数时间处于空闲状态,等待消息循环中的消息到来。一旦接收到消息,它们就进行处理并等待下一个消息到来。另外,辅助线程用来执行后台处理而且不使用消息循环。Windows 计时器和基于服务器的计时器在运行时都使用 Interval 属性。线程计时器的时间间隔在 Timer 构造函数中设置。计时器的设计目的各不相同,它们的线程处理明确地指出了这一点: 

Windows 计时器是为单线程环境设计的,其中,UI 线程用于执行处理。Windows 计时器的精度限定为 55 毫秒。这些传统计时器要求用户代码有一个可用的 UI 消息泵,而且总是在同一个线程中操作,或者将调用封送到另一个线程。对于 COM 组件来说,这样会降低性能。 

基于服务器的计时器是为在多线程环境下与辅助线程一起使用而设计的。由于它们使用不同的体系结构,因此基于服务器的计时器可能比 Windows 计时器精确得多。服务器计时器可以在线程之间移动来处理引发的事件。 

对消息不在线程上发送的方案中,线程计时器是非常有用的。例如,基于 Windows 的计时器依赖于操作系统计时器的支持,如果不在线程上发送消息,与计时器相关的事件将不会发生。在这种情况下,线程计时器就非常有用。

Windows 计时器位于 System.Windows.Forms 命名空间中,服务器计时器 System.Timers 命名空间中,线程计时器位于 System.Threading 命名空间中。

 

 

 

首先简单介绍一下timer,这里所说的timer是指的System.Timers.timer,顾名思义,就是可以在指定的间隔是引发事件。官方介绍在这里,摘抄如下:

 

1
2
Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可通过处理这个事件来提供常规处理。 例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。 如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。
     基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。

 

如果想了解跟其他的timer有啥区别,可以看这里,里面有详细的介绍,不再多说了(其实我也不知道还有这么多)。那使用这个计时器有啥好处呢?主要因为它是通过.NET Thread Pool实现的、轻量、计时精确、对应用程序及消息没有特别的要求。

★使用

 

下面就简单介绍一下,这个Timer是怎么使用的,其实很简单,我就采用微软提供的示例来进行测试,直接上代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Timer不要声明成局部变量,否则会被GC回收
  private  static  System.Timers.Timer aTimer;
 
  public  static  void  Main()
  {
      //实例化Timer类,设置间隔时间为10000毫秒;
      aTimer =  new  System.Timers.Timer(10000);
 
      //注册计时器的事件
      aTimer.Elapsed +=  new  ElapsedEventHandler(OnTimedEvent);
 
      //设置时间间隔为2秒(2000毫秒),覆盖构造函数设置的间隔
      aTimer.Interval = 2000;
 
      //设置是执行一次(false)还是一直执行(true),默认为true
      aTimer.AutoReset =  true ;
 
      //开始计时
      aTimer.Enabled =  true ;
 
      Console.WriteLine( "按任意键退出程序。" );
      Console.ReadLine();
  }
 
  //指定Timer触发的事件
  private  static  void  OnTimedEvent( object  source, ElapsedEventArgs e)
  {
      Console.WriteLine( "触发的事件发生在: {0}" , e.SignalTime);
  }

运行的结果如下,计时蛮准确的:

1
2
3
4
5
6
7
8
/*
按任意键退出程序。
触发的事件发生在: 2014/12/26 星期五 23:08:51
触发的事件发生在: 2014/12/26 星期五 23:08:53
触发的事件发生在: 2014/12/26 星期五 23:08:55
触发的事件发生在: 2014/12/26 星期五 23:08:57
触发的事件发生在: 2014/12/26 星期五 23:08:59
*/

★重入问题重现及分析

 

什么叫重入呢?这是一个有关多线程编程的概念:程序中,多个线程同时运行时,就可能发生同一个方法被多个进程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入会导致数据不一致的情况。Timer方法重入是指使用多线程计时器,一个Timer处理还没有完成,到了时间,另一Timer还会继续进入该方法进行处理。下面演示一下重入问题的产生(可能重现的不是很好,不过也能简单一下说明问题了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//用来造成线程同步问题的静态成员
private  static  int  outPut = 1;
//次数,timer没调一次方法自增1
private  static  int  num = 0;
 
private  static  System.Timers.Timer timer =  new  System.Timers.Timer();
 
public  static  void  Main()
{
     timer.Interval = 1000;
     timer.Elapsed += TimersTimerHandler;
     timer.Start();
 
     Console.WriteLine( "按任意键退出程序。" );
     Console.ReadLine();
}
 
/// <summary>
/// System.Timers.Timer的回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private  static  void  TimersTimerHandler( object  sender, EventArgs args)
{
     int  t = ++num;
     Console.WriteLine( string .Format( "线程{0}输出:{1},       输出时间:{2}" , t, outPut.ToString(),DateTime.Now));
     System.Threading.Thread.Sleep(2000);
     outPut++;
     Console.WriteLine( string .Format( "线程{0}自增1后输出:{1},输出时间:{2}" , t, outPut.ToString(),DateTime.Now));
}

下面显示一下输出结果:

 

 

是不是感觉上面输出结果很奇怪,首先是线程1输出为1,没有问题,然后隔了2秒后线程1自增1后输出为2,这就有问题了,中间为什么还出现了线程2的输出?更奇怪的是线程2刚开始输出为1,自增1后尽然变成了3!其实这就是重入所导致的问题。别急,咱们分析一下就知道其中的缘由了。

 

首先timer启动计时后,开启一个线程1执行方法,当线程1第一次输出之后,这时线程1休眠了2秒,此时timer并没有闲着,因为设置的计时间隔为1秒,当在线程1休眠了1秒后,timer又开启了线程2执行方法,线程2才不管线程1是执行中还是休眠状态,所以此时线程2的输出也为1,因为线程1还在休眠状态,并没有自增。然后又隔了1秒,这时发生同时发生两个事件,线程1过了休眠状态自增输出为2,timer同时又开启一个线程3,线程3输出的为线程1自增后的值2,又过了1秒,线程2过了休眠状态,之前的输出已经是2,所以自增后输出为3,又过了1秒……我都快晕了,大概就是这意思吧,我想表达的就是:一个Timer开启的线程处理还没有完成,到了时间,另一Timer还会继续进入该方法进行处理。

 

那怎么解决这个问题呢?解决方案有三种,下面一一道来,适应不同的场景,不过还是推荐最后一种,比较安全。

★重入问题解决方案

 

1、使用lock(Object)的方法来防止重入,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就等待执行,适用重入很少出现的场景(具体也没研究过,可能比较占内存吧)。

 

代码跟上面差不多,在触发的方法中加入lock,这样当线程2进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:

1
2
3
4
5
6
7
8
private  static  object  locko =  new  object (); 
  /// <summary>
  /// System.Timers.Timer的回调方法
  /// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private  static  void  TimersTimerHandler( object  sender, EventArgs args)
{
1
int  t = ++num; <br>         lock  (locko)
1
2
3
4
5
6
7
<em id= "__mceDel" >        {
             Console.WriteLine( string .Format( "线程{0}输出:{1},       输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
             System.Threading.Thread.Sleep(2000);
             outPut++;
             Console.WriteLine( string .Format( "线程{0}自增1后输出:{1},输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
         }
     }</em>

执行结果:

 

 

2、设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private  static  int  inTimer = 0;
/// <summary>
/// System.Timers.Timer的回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private  static  void  TimersTimerHandler( object  sender, EventArgs args)
{
     int  t = ++num;
     if  (inTimer == 0)
     {
         inTimer = 1;
         Console.WriteLine( string .Format( "线程{0}输出:{1},       输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
         System.Threading.Thread.Sleep(2000);
         outPut++;
         Console.WriteLine( string .Format( "线程{0}自增1后输出:{1},输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
         inTimer = 0;
     }
}

执行结果:

 

 

3、在多线程下给inTimer赋值不够安全,Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法(感觉比较高上大,也是比较推荐的一种方法),执行结果与方法2一样,也是放弃执行。Interlocked.Exchange用法参考这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private  static  int  inTimer = 0;
/// <summary>
/// System.Timers.Timer的回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private  static  void  TimersTimerHandler( object  sender, EventArgs args)
{
     int  t = ++num;
     if  (Interlocked.Exchange( ref  inTimer, 1) == 0)
     {
         Console.WriteLine( string .Format( "线程{0}输出:{1},       输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
         System.Threading.Thread.Sleep(2000);
         outPut++;
         Console.WriteLine( string .Format( "线程{0}自增1后输出:{1},输出时间:{2}" , t, outPut.ToString(), DateTime.Now));
         Interlocked.Exchange( ref  inTimer, 0);
     }
}

执行结果:

1
<img style= "BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px"  title= "image"  border= "0"  alt= "image"  src= "/upload/201412/30/270111349058497.png"  width= "640"  height= "179" >

★总结

 

从别处拷贝过来,留存自己学习

 
 

 

转载于:https://www.cnblogs.com/kglovekk/p/9556757.html

相关文章:

  • @ConfigurationProperties注解对数据的自动封装
  • Java 中创建子类对象会创建父类对象么?
  • 通过PXE部署系统时报错 0xc000000f
  • RabbitMQ之消息确认机制(事务+Confirm)
  • 大屏FAQ
  • java源码 - CountDownLatch
  • 推荐几十本DBA学习的书
  • 利用 Siblings一步实现多个同级div,只改变当前点击的div样式
  • 前端笔记-201808
  • 设置PHP最长运行时间
  • zabbix添加nginx中间件监控
  • 将MWeb的文章发布到自己做的网站(超级详细)
  • SELinux 宽容模式(permissive) 强制模式(enforcing) 关闭(disabled) 几种模式之间的转换...
  • 如何把本地文件上传到gitlab上已存在的工程里
  • Spring事务隔离级别详解
  • 【剑指offer】让抽象问题具体化
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Angular Elements 及其运作原理
  • const let
  • Effective Java 笔记(一)
  • es6
  • JavaScript设计模式与开发实践系列之策略模式
  • JSONP原理
  • nodejs实现webservice问题总结
  • quasar-framework cnodejs社区
  • STAR法则
  • vue数据传递--我有特殊的实现技巧
  • Vue小说阅读器(仿追书神器)
  • 不上全站https的网站你们就等着被恶心死吧
  • 分布式熔断降级平台aegis
  • 构建二叉树进行数值数组的去重及优化
  • 基于axios的vue插件,让http请求更简单
  • 简单易用的leetcode开发测试工具(npm)
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 区块链分支循环
  • 如何用vue打造一个移动端音乐播放器
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (rabbitmq的高级特性)消息可靠性
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (多级缓存)多级缓存
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (利用IDEA+Maven)定制属于自己的jar包
  • (三)Honghu Cloud云架构一定时调度平台
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)