.NET中的三种Timer的区别和用法(转)

最近正好做一个WEB中定期执行的程序,而.NET中有3个不同的定时器。所以正好研究研究。这3个定时器分别是: 

//1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。 

System.Windows.Forms.Timer 


//
2.提供以指定的时间间隔执行方法的机制。无法继承此类。 

System.Threading.Timer 



//3.在应用程序中生成定期事件。 

System.Timers.Timer 



这三个定时器位于不同的命名空间内,上面大概介绍了3个定时器的用途,其中第一个是只能在Windows窗体中使用的控件。在.NET1.1里面,第3个
System.Timers.Timer,也是可以拖拽使用,而.NET2.0开始取消了,只能手动编写代码。而后2个没有限制制。下面通过具体的列子来
看3个Timer的使用和区别,网上谈的很多,但基本都没有代码。 



System.Windows.Forms.Timer 



#region System.Windows.Forms.Timer 

public partial class Form1 : Form 



public Form1() 



InitializeComponent(); 




int
num = 0; 



private void Form_Timer_Tick(object sender, EventArgs e) 



label1.Text = (++num).ToString(); 

Thread.Sleep(3000); 





private void button1_Click(object sender, EventArgs e) 



Form_Timer.Start(); 





private void button2_Click(object sender, EventArgs e) 



Form_Timer.Stop(); 





#endregion 

上面这个是一个很简单的功能,在Form窗体上拖了一个System.Windows.Forms.Timer控件名字为Form_Timer,在属性窗
中把Enable属性设置为Ture,Interval是定时器的间隔时间。双击这个控件就可以看到
Form_Timer_Tick方法。在这个方法中,我们让她不停的加一个数字并显示在窗体上,2个按钮提供了对计时器的控制功能。 



执行的时候你去点击其他窗体在回来,你会发现我们的窗体失去响应了。因为我们这里使用Thread.Sleep(3000);让当前线程挂起,而UI失去相应,说明了这里执行时候采用的是单线程。也就是执行定时器的线程就是UI线程。 

Timer 用于以用户定义的事件间隔触发事件。Windows 计时器是为单线程环境设计的,其中,UI 线程用于执行处理。它要求用户代码有一个可用的 UI
消息泵,而且总是在同一个线程中操作,或者将调用封送到另一个线程。 



在Timer内部定义的了一个Tick事件,我们前面双击这个控件时实际是增加了一行代码 



this.Form_Timer.Tick += new System.EventHandler(this.Form_Timer_Tick); 

这个应该明白,不明白的可以看我BLOG中有关委托和事件的文章。然后Windows将这个定时器与调用线程关联(UI线程)。当定时器触发
时,Windows把一个定时器消息插入到线程消息队列中。调用线程执行一个消息泵提取消息,然后发送到回调方法中(这里的
Form_Timer_Tick方法)。而这些都是单线程进行了,所以在执行回调方法时UI会假死。所以使用这个控件不宜执行计算受限或IO受限的代码,
因为这样容易导致界面假死,而应该使用多线程调用的Timer。另外要注意的是这个控件时间精度不高,精度限定为 55
毫秒。我们把Interval设置为20ms,然后在start和stop方法中记录当前时,并计算出运行时间: 



从上面图可以看到程序执行了7.8S也就是
7800ms,而间隔时间是20ms,也就是最后显示数字应该是390左右,但只有250,显然是不准确的,不过按MSDN说的55ms的精度,7800ms应该只执行了140多次或更少。不知道这里是不是理解有问题。 


System.Timers.Timer 



接下来就看下另一个Timer,我们用他来改写上面的程序 



#region System.Windows.Forms.Timer 

public partial class Form1 : Form 



public Form1() 



InitializeComponent(); 




int
num = 0; 

DateTime time1 = new DateTime(); 

DateTime time2 = new DateTime(); 

//定义Timer 

System.Timers.Timer Timers_Timer = new System.Timers.Timer(); 



private void button1_Click(object sender, EventArgs e) 



//手动设置Timer,开始执行 

Timers_Timer.Interval = 20; 

Timers_Timer.Enabled = true; 

Timers_Timer.Elapsed += new
System.Timers.ElapsedEventHandler(Timers_Timer_Elapsed); 

time1 = DateTime.Now; 





void Timers_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs
e) 



label1.Text = Convert.ToString((++num)); //显示到lable 

Thread.Sleep(3000); 





private void button2_Click(object sender, EventArgs e) 



//停止执行 

Timers_Timer.Enabled = false; 

time2 = DateTime.Now; 

MessageBox.Show(Convert.ToString(time2-time1)); 





#endregion 

我们可以看到这个代码和前面使用Form.Timer的基本相同,不同的是我们是手动定义的对象,而不是通过拉控件。他也有Interval ,Enabled
等属性,作用和第一是一样的。不同的是他的事件名为Elapsed
,但是和上面的Tick一样,绑定一个委托的方法。只是这里我们是手动完成的。另外不同之处是Form.Timer我们可以用Stop和Start方法控
制,而这里是通过Enable属性控制。但实际上也可以用Stop和Start方法,内部也是通过他自己的Enable来控制的。

最大的不同就是上面的代码在调试时会报错,提示你"线程间操作无效:
从不是创建控件“label1”的线程访问它。"但如果你不调试直接运行是OK的,而且运行时你去拖动窗体会发现没有出现假死。从这里我们就可以知道这里
的Timer的创建线程和执行线程不是同一个线程。也就是使用了多线程。Timer的创建线程是UI线程,而执行线程是TheardPool中的线程,所
以不会假死,但调试的时候会报错,因为非控件的创建线程不能操作控件。但你可以直接运行,这里是VS05做了手脚。解决办法很多,用
delegate.BeginInvoke()等等。这里介绍特有的一种方法,设置Timer的SynchronizingObject属
性,Timers_Timer.SynchronizingObject =
label1;这样的话,我们的话,调试运行时就不会报错了,但是设置了这个属性Timer就编程单线程调用了,就基本和第一个完全一样了。

Timer 是为在多线程环境中用于辅助线程而设计的。服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows
计时器更精确地按时引发事件。Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个
ThreadPool 线程上将会再次引发此事件。因此,事件处理程序应当是可重入的。 



另外和前面不同的现象是每次加1后并没有停止3秒在显示。而是继续显示,只是速度稍慢。因为我们设置间隔为20ms,而执行时间为3s,所以会在20ms后在另一个线程中继续执行,而当前线程被挂起而已。关于计时器的精度,取消3s的挂起,发现结果和第一个基本一致。 


System.Threading.Timer 



继续用这个对象改造程序。 



#region System.Windows.Forms.Timer 

public partial class Form1 : Form 



public Form1() 



InitializeComponent(); 




int
num = 0; 

DateTime time1 = new DateTime(); 

DateTime time2 = new DateTime(); 

System.Threading.Timer Thread_Time; 



private void button1_Click(object sender, EventArgs e) 



//启动 

Thread_Time = new
System.Threading.Timer(Thread_Timer_Method,null,0,20); 

time1 = DateTime.Now; 







void Thread_Timer_Method(object o) 



label1.Text = Convert.ToString((++num)); 

System.Threading.Thread.Sleep(3000); 





private void button2_Click(object sender, EventArgs e) 



//停止 

Thread_Time.Dispose(); 

time2 = DateTime.Now; 

MessageBox.Show(Convert.ToString(time2-time1)); 





#endregion 

用Threading.Timer时的方法,和前面就不太相同了,所以的参数全部在构造函数中进行了设置,而且可以设置启动时间。而且没有提供start和stop方法来控制计时器。而且是以一种回调方法的方式实现,而不是通过事件来实现的。他们之间还是有区别的。 

我们只有销毁掉对象来停止他。当你运行时,你会发现他和前面的Timers.Timer一样,是多线程的,主要表现在不会假死,调试运行报错。但跟让你奇
怪的是,我们的代码竟然无法让她停止下来。调用了Dispose方法没有用。问题在那?然后有进行了测试,修改了间隔时间为
100,200,500,1000,3000,4000。这几种情况。发现当间隔为500ms以上是基本马上就停止了。而间隔时间相对执行时间越短,继续
执行的时间越长。这应该是在间隔时间小于执行时间时多个线程运行造成的。因为所有的线程不是同时停止的。间隔越短,线程越多,所以执行次数越多。 

最后来看下这个对象另外一个特殊的地方。 



static void Main() 



Timer t = new Timer(Test,null,0,1000); 

Console.ReadLine(); 





public static void Test(object o) 



Console.WriteLine("nihao"); 

GC.Collect(); 



这段代码会输出什么结果呢?默认情况他只输出一次,就停止了。为什么呢?根据上面说的,当定义对象t,执行代码后,进行了强制垃圾回收,因为t在Main
中没有其他引用,所以被回收掉了。但是如果我们吧编译器的”优化“项取消掉,在看看情况。程序进然一直在输出。为什么执行垃圾回收却没有被回收呢?因为这
个禁用优化选项,t的声明周期被扩展到了方法结束。所以一直执行。 



因为编译器默认是优化的,所以我们必须保证Timer对象一直被引用,而避免被垃圾回收。所以我们可以在编译器打开优化的情况下,在Main函数最后加上
t=null保证回收前被引用,但你发现,这样是没用的。因为JIT编译器优化后会吧t=null直接删除,所以我们用t.Dispose(),就可以达
到目的。在我们进行垃圾回收时,CLR发现t还有被引用,还没执行Dispose所以不会被回收。是以Threading.Timer有时候会出现运行一
次就停止或者是销毁了还在运行的情况,而且和编译器优化也有关,所以使用时要注意。 



最后看下MSDN的描述: 只要在使用 Timer,就必须保留对它的引用。对于任何托管对象,如果没有对 Timer 的引用,计时器会被垃圾回收。即使 Timer
仍处在活动状态,也会被回收。当不再需要计时器时,请使用 Dispose 方法释放计时器持有的资源。如果希望在计时器被释放时接收到信号,请使用接受
WaitHandle 的 Dispose(WaitHandle) 方法重载。计时器已被释放后,WaitHandle 便终止。 



总结: 



System.Threading.Timer 是一个简单的轻量计时器,它使用回调方法并由线程池线程提供服务。不建议将其用于 Windows
窗体,因为其回调不在用户界面线程上进行。System.Windows.Forms.Timer 是用于 Windows
窗体的更佳选择。要获取基于服务器的计时器功能,可以考虑使用 System.Timers.Timer,它可以引发事件并具有其他功能。 

在《CLR Via
C#》中讲多线程时有提到这3个计时器,但作者说System.Timers.Timer是对System.Threading.Timer的报装,不推
荐使用,但是在我的WEB项目中的Application_Start中我还是使用的这个而不是Threading.Timer,因为使用
Threading.Timer时只执行了一次就不在执行了。 



对于计时器在B/S结构中的使用就复杂一些,一般我们把计时器放在Application_OnStart中,这样全局维护一个计时器,可以进行定期备份
数据库,定期维护用户等操作,而且方法写作静态的,以免被垃圾回收。而不建议在一般的aspx页面中使用,因为服务器端的定时器对用户这样意义不大,完全
可以使用JS代替。而且这个页面的每个请求都可能引入一个新的定时器,导致系统崩溃。另外,定时器是ASP.NET进程,IIS有关,所以对用重要的执行
任务,还是建议写成服务或独立程序放在服务器上执行好了。 



为什么ASP.NET的Application_OnStart中使用Threading.Timer不能循环执行还不清楚,难道是GC回收或是IIS线程的问题?

.NET中的三种Timer的区别和用法(转),布布扣,bubuko.com

时间: 2024-10-01 11:16:20

.NET中的三种Timer的区别和用法(转)的相关文章

.NET中的三种Timer的区别和用法

//1.实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用. System.Windows.Forms.Timer // 2.提供以指定的时间间隔执行方法的机制.无法继承此类. System.Threading.Timer //3.在应用程序中生成定期事件. System.Timers.Timer 这三个定时器位于不同的命名空间内,上面大概介绍了3个定时器的用途,其中第一个是只能在Windows窗体中使用的控件.在.NET1.1里面,

JavaScript中的三种弹出对话框

JavaScript中的三种弹出对话框 *****本文来自互联网****** 学习过js的小伙伴会发现,我们在一些实例中用到了alert()方法.prompt()方法.prompt()方法,他们都是在屏幕上弹出一个对话框,并且在上面显示括号内的内容,使用这种方法使得页面的交互性更精彩,实际上我们经常会在进行网页浏览时简单这种类型的对话框,在用户与应用程序进行双向交流时,经常要用到对话框.avascript的三种对话框是通过调用window对象的三个方法alert(),confirm()和prom

js oop中的三种继承方法

JS OOP 中的三种继承方法: 很多读者关于js opp的继承比较模糊,本文总结了oop中的三种继承方法,以助于读者进行区分. <继承使用一个子类继承另一个父类,子类可以自动拥有父类的属性和方法.(继承的两方,发生在两个类之间)> 一.通过object实现继承 1:定义父类 function Parent(){} 2:定义子类 funtion Son(){} 3:通过原型给Object对象添加一个扩展方法. Object.prototype.customExtend = function(p

虚拟机NetworkAdapter三种方式的区别

虚拟机在安装时默认的有三块网卡,VMnet1和VMnet8,另外还有VMnet0 Vmware 还提供了三种网络连接模式: 分别为: A 桥接bridge  B NAT 网络地址转换 C  主机模式 下面来简单说一下 三种方式的区别  : 1) bridge : 默认使用VMnet0,不提供DHCP服务 在桥接模式下,虚拟机和宿主计算机处于同等地位,虚拟机就像是一台真实主机一样存在于局域网中.因此在桥接模式下,我们就要像对待其他真实计算机一样为其配置IP.网关.子网掩码等等.当我们可以自由分配局

.NET中的三种接口实现方式

摘自:http://www.cnblogs.com/zhangronghua/archive/2009/11/25/1610713.html 一般来说.NET提供了三种不同的接口实现方式,分别为隐式接口实现.显式接口实现.混合式接口实现.这三种方式各有各的特点. 首先来看隐式接口实现,这恐怕是我们使用最多的一种接口实现,因为隐匿接口实现是.NET的默认接口实现方式.下面让我们来看一个隐式接口实现的例子: using System; internal class MyClass { public

selenium中的三种等待方式(显示等待WebDriverWait()、隐式等待implicitly()、强制等待sleep())---基于python

我们在实际使用selenium或者appium时,等待下个等待定位的元素出现,特别是web端加载的过程,都需要用到等待,而等待方式的设置是保证脚本稳定有效运行的一个非常重要的手段,在selenium中(appium通用)常用的等待分为显示等待WebDriverWait().隐式等待implicitly_wait().强制等待sleep()三种,下面我们就分别介绍一下这三种等待的区别 在前面的博文中简单介绍了<强制等待和隐士等待的区别和理解>,本文再详细的结合案例进行理解. sleep(): 强

百科知识 pal,ntsc,szcam三种制式的区别

比较pal,ntsc,szcam三种制式的区别 快!!!!!比较pal,ntsc,szcam三种制式的区别 最佳答案 世界上现行的彩色电视制式有三种:NTSC(National Television System Committee)制(简称N制).PAL(Phase Alternation Line)制和SECAM制. http://www.blhvideo.com/data/2006/0406/article_45.htm ? ? 有关录像机的制式与伴音常识 http://cgi.ebay.

关于PHP、JAVA、.NET这三种技术的区别

关于PHP.JAVA..NET这三种技术的区别 一.语言: PHP:PHP产生与1994年,其语法混合了C.Java.Perl 和他自创的一些编程语法:PHP是嵌入在HTML中执行的:它也是一种解释性语言.早期的PHP并非完全的面向对象编程语言,到了PHP4以后的版本才开始有了面向对象的概念. JAVA:JAVA产生与1995年,JAVA语言和JAVA平台统称为JAVA:它语法与C语言和C++语言很接近,并且JAVA是面向对象编程语言,JAVA是编译性语言,可以先将JAVA源码编译成.class

C#里面的三种Timer

 在.net中有三种计时器,一是System.Windows.Forms命名空间下的Timer控件,它直接继承自Componet;二是System.Timers命名空间下的Timer类. Timer控件:Timer控件只有绑定了Tick事件,和设置Enabled=True后才会自动计时,停止计时可以用Stop()控制,通过Stop()停止之后,如果想重新计时,可以用Start()方法来启动计时器.Timer控件和它所在的Form属于同一个线程: System.Timers.Timer类:定义