你所在位置:首页C#.net开发 → 定时器:.NET Framework类库中的Timer类比较

定时器:.NET Framework类库中的Timer类比较

发布时间:2019-04-15

概要

在客户端程序和服务器(包括windows服务)中,timer(定时器)通常扮演着一个重要角色。编写高效的timer驱动的托管代码,需要对程序流程和.net线程模型的精妙有清晰的理解。.NET Framework 类库提供了三个不同的timer类:System.Windows.Forms.Timer, System,Timers.Timer 和 System.Threading.Timer。每个Timer类被设计优化用于不同的场合。本文研究了这三个Timer类,帮组你理解如何及何时该使用哪个类。

 

目录

System.Windows.Forms.Timer

System.Timers.Timer

System.Threading.Timer

定时器的线程安全

处理timer事件的重入

结论

 

Microsoft® Windows® 中的Timer对象在行为发生时允许你进行控制。Timer最常见的一些用法是有规律的定时启动一个进程,设置事件发生的间隔,在处理图像时维持一致的动画速度(不管处理器的速度如何)。在过去,对于使用Visual Basic®的开发人员来说, Timer甚至能用来模拟多任务。

如你所想,微软.NET Framework为你提供了处理这些任务所需的工具。在.NET Framework类库中有三个不同的Timer类:System.Windows.Forms.Timer, System,Timers.Timer 和 System.Threading.Timer。前两个类出现在Visual Studio® .NET 工具箱中,你可以直接把它们拖拽到Windows窗体设计器或组件设计器。如果你不小心,这时麻烦就开始了。

Visual Studio .NET 工具箱在Windows窗体页和组件页都有一个Timer控件(见图1)。很容易就用错了,或者更糟的是没有认识到它们是不同的。仅当目标是Windows窗体设计器时,使用Windows窗体页上的Timer控件。这个控件会在你的窗体上放置一个System.Windows.Forms.Timer类的实例。正如工具箱中的其它控件,你可以让Visual Studio .NET自动生成,或者也可以自己手工实例化、初始化这个类。

 

 


图1 定时器控件

 

组件页上的Timer控件可以安全地用于任何类。这个控件会创建System.Timers.Timer类的实例。如果你使用Visual Studio .NET 工具箱,无论是在Windows窗体设计器,还是在组件设计器,你都可以安全的使用这个Timer。当你处理一个继承自System.ComponentModel.Component的类(例如处理Windows服务)时,Visual Studio .NET会使用组件设计器。System.Threading.Timer类不在Visual Studio .NET 工具箱中。它稍微复杂些,但也提供了更高级的控制,稍后你将在本文看到。

 

我们首先研究一下System.Windows.Forms.Timer和System.Timers.Timer。这两个类有非常相似的对象模型。一会儿我会探究更高级的Sytem.Threading.Timer类。图2是我在这篇文章中引用的例子程序的屏幕截图。这个程序将帮助你更清晰的认识这几个类。你可以从文章上方的链接下载完整代码,并用它做试验。

 

 


图2 例子程序

 

System.Windows.Forms.Timer

如果你在找一个节拍器,你就找错地方了。这个Timer类触发的定时器事件与你的Windows窗体应用程序的其余代码是同步的。这就是说,正在执行的应用程序代码永远也不会被这个Timer类的实例抢占(假定你没有调用Application.DoEvents)。就像一个典型的Windows窗体应用程序的其余代码一样,这种Timer类的Timer事件处理器中的任何代码都是使用应用程序的UI线程来执行。在空闲时间,UI线程也负责处理应用程序的Windows消息队列中的所有消息,其中包括Windows API消息,也包括这种Timer类触发的Tick事件。当应用程序没有忙于做其他事时,UI线程就处理这些消息。

如果你在Visual Studio .NET之前,写过VB代码,你可能知道在基于Windows的应用程序中,允许UI线程在执行事件处理器时响应Windows消息的唯一方法,就是调用Application.DoEvents方法。正如VB中一样,在.NET Framework中调用Application.DoEvents会导致一些问题。Application.DoEvents移交控制给UI消息泵,允许对所有未处理的事件进行处理。这会改变我刚才提到的程序执行路径。如果在你代码里调用Application.DoEvents,你的程序流程会被中断,以便处理这个Timer类的实例所触发的定时器事件。这会导致不可预料的行为,使调试变得困难。

当我们执行例子程序,这个Timer类的行为就明显了。慊骼映绦虻腟tart按钮,然后点击Sleep按钮,最后点击Stop按钮,将会产生下面的输出:

System.Windows.Forms.Timer Started @ 4:09:28 PM

--> Timer Event 1 @ 4:09:29 PM on Thread: UIThread

--> Timer Event 2 @ 4:09:30 PM on Thread: UIThread

--> Timer Event 3 @ 4:09:31 PM on Thread: UIThread

Sleeping for 5000 ms...

--> Timer Event 4 @ 4:09:36 PM on Thread: UIThread

System.Windows.Forms.Timer Stopped @ 4:09:37 PM

 

例子程序把System.Windows.Forms.Timer类的Interval属性设置为1000毫秒。正如你看到的,如果在主UI线程休眠(5秒)时,timer事件处理器继续捕获timer事件,那么一旦UI线程再次被唤醒时,就应该显示5个timer事件——UI线程休眠时每秒钟一个。然而,在UI线程休眠时,timer处于挂起状态。

用System.Windows.Forms.Timer编程已经够简单了——它有一个非常简单直观的编程接口。Start和Stop方法提供了一个设置Enable属性(对Win32® SetTimer/ KillTimer 函数的轻量级封装)的替代方法。刚才提到的Interval属性,是不言自明的。尽管技术上,你可以把Interval属性设置得低到一毫秒,但你应该知道.NET Framework文档中说这个属性只能精确到大约55毫秒(假设UI线程可用于处理)。

捕获System.Windows.Forms.Timer类的实例触发的事件,是通过把Tick事件关联到标准的EventHandler代理来实现的,如下面例子中的代码片段所示:

System.Windows.Forms.Timer tmrWindowsFormsTimer = new

    System.Windows.Forms.Timer();

tmrWindowsFormsTimer.Interval = 1000;

tmrWindowsFormsTimer.Tick += new

    EventHandler(tmrWindowsFormsTimer_Tick);

tmrWindowsFormsTimer.Start();

...

private void tmrWindowsFormsTimer_Tick(object sender,

    System.EventArgs e) {

  //Do something on the UI thread...

}

 

System.Timers.Timer

.NET Framework文档之处System.Timers.Timer是一个基于服务器的定时器,是为多线程环境进行设计和优化的。这个Timer类的实例可以从多线程中安全的访问。不像System.Windows.Forms.Timer,System.Timers.Timer类默认会从公共语言运行时的线程池获取一个工作线程(worker thread)来调用你的timer事件处理器。这意味着你的Elapsed事件处理器中的代码必须遵守Win32编程的黄金规则:控件的实例绝不能被除实例化它的线程以外的任何其他线程访问。

System.Timers.Timer类提供了一个简单的方式处理这样的困境——它暴露了一个公有的SynchronizingObject属性。把这个属性设置成Windows窗体的一个实例(或Windows窗体上的一个控件),可以保证你的Elapsed事件处理器中的代码运行在SynchronizingObject 被实例化的同一个线程。

如果你使用Visual Studio .NET工具箱,Visual Studio .NET会自动把SynchronizingObject属性设置为当前窗体。起初可能看起来,使用有SynchronizingObject属性的这个Timer类,使其在功能上与使用System.Windows.Forms.Timer等同。对于大部分功能,确实是这样。当操作通知System.Timers.Timer类启用的定时时间已过,定时器使用SynchronizingObject.BeginInvoke方法在创建SynchronizingObject的底层handle的线程上执行Elapsed事件代理。事件处理器会被阻塞,直到UI线程能处理它。然而,不像 System.Windows.Forms.Timer,事件最终还是会被触发。就像你在图2看到的,当UI线程不能处理时, System.Windows.Forms.Timer不会触发事件。而System.Timers.Timer会把事件排到队列中,等待UI线程可用时进行处理。

图3显示了如何使用SynchronizingObject 属性。你可以使用例子程序分析这个类,选择 System.Timers.Timer 单选按钮,按照执行System.Windows.Forms.Timer同样的顺序执行这个类。这样做会产生图4所示输出。

 

图3 使用SynchronizingObject属性

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

tmrTimersTimer.Interval = 1000;

tmrTimersTimer.Elapsed += new

    ElapsedEventHandler(tmrTimersTimer_Elapsed);

tmrTimersTimer.SynchronizingObject = this; //Synchronize with

                                           //the current form...

tmrTimersTimer.Start();

……

private void tmrTimersTimer_Elapsed(object sender,

    System.Timers.ElapsedEventArgs e) {

  // Do something on the UI thread (same thread the form was

  // created on)...

  // If we didnt set SynchronizingObject we would be on a

  // worker thread...

}
上一篇:C#编写的Windows窗体程序怎样打包、卸载
下一篇:C# webservice调用方法总结