三种Timer的区别及使用

来源:互联网 发布:淘宝微淘设置 编辑:程序博客网 时间:2024/09/21 09:21

三种Timer控件的区别

1、 System.Windows.Form.Timer:

基于UI层的计时器,与UI在同一个线程,在timer的事件处理中,UI层失去响应。单线程组件,精度限定为 55 毫秒。事件由Tick触发。

2、 System.Timers.Timer:

基于服务器的计时器,与UI不在同一个线程。多线程组件。Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程上将会再次引发此事件。因此,事件处理程序应当是可重入的。

3、 System.Threading.Timer:

基于线程的计时器,是一个简单的轻量计时器,使用 TimerCallback 委托指定希望 Timer 执行的方法,由计时器执行的回调方法应该是可重入的,因为它是在 ThreadPool

 


-----------------------------------------------------------------------------

 

基于服务器的计时器允许指定在应用程序中引发事件的重复时间间隔。然后可通过处理这个事件来提供常规处理。例如,假设您有一台关键的服务器,必须保持一周 7 天、一天 24 小时连续运行。您可以创建一个服务,通过使用计时器来定期检查关键的服务器,确保系统启动并运行。如果该系统没有响应,此服务可以尝试重新启动服务器或通知系统管理员。

注意 以毫秒为单位指定服务器计时器的时间间隔。
服务器计时器、Windows 计时器和线程计时器
在 Visual Studio .NET 和 .NET Framework 中有三种计时器控件:基于服务器的计时器,位于“工具箱”的“组件”选项卡上;基于 Windows 的标准计时器,位于“工具箱”的“Windows 窗体”选项卡上,以及仅可在编程时使用的线程计时器。基于 Windows 的计时器从 Visual Basic 的 1.0 版起就存在于该产品中并且基本上保持不变。该计时器已经为在 Windows 窗体应用程序中使用而进行了优化。基于服务器的计时器是传统的计时器为了在服务器环境上运行而优化后的更新版本。线程计时器是一种简单的、轻量级计时器,使用回调方法而不是事件,并由线程池线程提供。

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

Windows 计时器是为单线程环境设计的,其中,UI 线程用于执行处理。Windows 计时器的精度限定为 55 毫秒。这些传统计时器要求用户代码有一个可用的 UI 消息泵,而且总是在同一个线程中操作,或者将调用封送到另一个线程。对于 COM 组件来说,这样会降低性能。
基于服务器的计时器是为在多线程环境下与辅助线程一起使用而设计的。由于它们使用不同的体系结构,因此基于服务器的计时器可能比 Windows 计时器精确得多。服务器计时器可以在线程之间移动来处理引发的事件。
对消息不在线程上发送的方案中,线程计时器是非常有用的。例如,基于 Windows 的计时器依赖于操作系统计时器的支持,如果不在线程上发送消息,与计时器相关的事件将不会发生。在这种情况下,线程计时器就非常有用。
Windows 计时器位于 System.Windows.Forms 命名空间中,服务器计时器位于 System.Timers 命名空间中,而线程计时器位于 System.Threading 命名空间中。

 

 

 

 

----------------------------------------------------------------------------------------------------------------

 

最近正好做一个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就编程单线程调用了,就基本和第一个完全一样了。

原创粉丝点击