问题:System.Timers 事件处理程序应当是可重入的
Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。 然后可通过处理这个事件来提供常规处理。 例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。 如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。
基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。
基于 Interval 属性的值,Timer 组件引发 Elapsed 事件。 您可以处理这个事件来执行必要的处理。 例如,假设您有一个联机销售应用程序,它持续向一个数据库投递销售订单。 编译要传送的指令的服务成批处理订单,而不是单独地处理每份订单。 可以使用 Timer 每 30 分钟启动一次批处理。
![]() |
---|
当 AutoReset 设置为 false 时,Timer 只在第一个 Interval 过后引发一次 Elapsed 事件。 若要保持以 Interval 时间间隔引发 Elapsed 事件,请将 AutoReset 设置为 true。 |
在 .NET Framework 2.0 版及更早版本中,Timer 组件捕获 Elapsed 事件的事件处理程序所引发的所有异常并禁止显示这些异常。 在将来的 .NET Framework 版本中,此行为可能会有更改。
如果 SynchronizingObject 属性为 null,则在 ThreadPool 线程上引发 Elapsed 事件。 如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程上将会再次引发此事件。 在这种情况下,事件处理程序应当是可重入的。
![]() |
---|
在一个线程调用 Stop 方法或将 Enabled 属性设置为 false 的同时,可在另一个线程上运行事件处理方法。 这可能导致在计时器停止之后引发 Elapsed 事件。 Stop 方法的代码示例演示了一种避免此争用条件的方法。 |
即使 SynchronizingObject 不是 null,Elapsed 在 Dispose 或 Stop 方法调用后或 Enabled 属性设置为 false 后,事件可以发生,这是因为引出 Elapsed 事件的信号总是排队等待执行线程池线程。 解决此争用条件的一种方法是:设置一个标志,通知 Elapsed 事件的事件处理程序忽略随后的事件。
如果您使用 Timer 与用户界面元素如窗体或控件,没有放置计时器的该用户界面元素,指定包含 Timer 窗体或控件到 SynchronizingObject 属性,以使该事件封送到用户界面线程。
Timer 在运行时是不可见的。
有关 Timer 的实例的初始属性值列表,请参见 Timer 构造函数。
![]() |
---|
应用到此类型或成员的 HostProtectionAttribute 特性具有以下 Resources 属性值:Synchronization | ExternalThreading。HostProtectionAttribute 不影响桌面应用程序(桌面应用程序一般通过双击图标、键入命令或在浏览器中输入 URL 启动)。有关更多信息,请参见 HostProtectionAttribute 类或 SQL Server 编程和宿主保护特性。 |
下面的代码示例为 Timer.Elapsed 事件设置一个事件处理程序、创建计时器并启动该计时器。 每次引发时,事件处理程序都会显示 SignalTime 属性。
![]() |
---|
该代码在类级别和 Main 内部包含了计时器变量的声明。 要了解垃圾回收如何影响在长时间运行方法内部声明的计时器,可以注释掉类级别声明和取消注释本地变量。 为了保持收集计时器,取消注释 GC.KeepAlive 方法于 Main 的末尾。 |
解决方案:
关于C#中timer类 在C#里关于定时器类就有3个
1.定义在System.Windows.Forms里
2.定义在System.Threading.Timer类里
3.定义在System.Timers.Timer类里
System.Windows.Forms.Timer是应用于WinForm中的,它是通过Windows消息机制实现的,类似于VB或Delphi中的Timer控件,内部使用API SetTimer实现的。它的主要缺点是计时不精确,而且必须有消息循环,Console Application(控制台应用程序)无法使用。
System.Timers.Timer和System.Threading.Timer非常类似,它们是通过.NET Thread Pool实现的,轻量,计时精确,对应用程序、消息没有特别的要求。System.Timers.Timer还可以应用于WinForm,完全取代上面的Timer控件。它们的缺点是不支持直接的拖放,需要手工编码。
例:
使用System.Timers.Timer类
System.Timers.Timer t = new System.Timers.Timer(10000);//实例化Timer类,设置间隔时间为10000毫秒;
t.Elapsed += new System.Timers.ElapsedEventHandler(theout);//到达时间的时候执行事件;
t.AutoReset = true;//设置是执行一次(false)还是一直执行(true);
t.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
public void theout(object source, System.Timers.ElapsedEventArgs e)
{
MessageBox.Show( "OK! ");
}
关于定时器
定时器是个很有意思的东西,它很有用,但我认为这不是现代计算机的结构所擅长的事情。
计算机适合做那些很大量的简单重复工作,或者根据请求做出回应。
DOS时代是没有进程线程等概念的,那时候要想做到定时真是有些麻烦
通常的做法是死循环不断监测时间,发现时间到了就做特定的事情
当然你可以用delay,来指定等待多长时间,但是如果你一边要响应用户的操作,比如输入,一边要定时做些
事情就是一件麻烦的事了
当然有些人可以这样做,截取系统的时钟中断(我忘了中断号是多少了),每秒钟有18.2次
当这些做法都不是很优雅。但DOS时代只能这样凑合着了
Windows是个伟大的进步,系统提供了Timer支持,但是问题是这个定时器并不准时而且有时候根本不能用。
Win32 API中有个SetTimer函数,可以为一个窗口创建一个定时器,这个定时器会定时产生消息WM_TIMER也可以调用
指定的回调函数,其实这都是一样的,因为都是单线程的。
单线程的定时器会有很多问题,首先是不准时,定时器只是定时把消息WM_TIMER访到线程的消息队列里,但是并不保证消息会立刻被响应,如果
碰巧系统比较忙,那么消息可能会在队列里放一端时间才被响应,还会造成本来应该间隔一段时间发生的消息响应连续发生了
解决方法通常是
OnTimer(...)
{
//Timer process.....
MSG msg;
While(PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE));
}
在当前Timer处理中,把消息队列里的WM_TIMER消息,清除掉。
更糟的是如果你不去调用GetMessage,那么就不会有Timer发生了。
这个问题直到win xp都没什么改变,似乎微软并不打算在Win32 API中解决这个问题了。
.NET Framework为我们带来了新的解决方案
.NET Framework提供三种Timer
Server Timers System.Timers.Timer
Thread Timers System.Threading.Timer
Windows Timers System.Windows.Forms.Timer
其中Windows Timers只是提供了和WinAPI 一样的Timer,仍然是基于消息,仍然是单线程
其它两个就不同了,他们是基于线程池的Thread Pool,这样最大的好处在于,产生的时间间隔准确均匀。
Server Timers 和 Thread Timers 的不同在于ServerTimers 是基于事件的,Thread Timers是基于回调函数
我更喜欢Thread Timer,比较轻量级方便易用。
但是这样的Timer也有问题,就是由于时多线程定时器,就会出现如果一个Timer处理没有完成,到了时间下一个
照样会发生,这就会导致重入问题
对付重入问题通常的办法是加锁,但是对于 Timer却不能简单的这样做,你需要评估一下
首先Timer处理里本来就不应该做太需要时间的事情,或者花费时间无法估计的事情,比同远方的服务器建立一个网络连接,这样的做法尽量避免
如果实在无法避免,那么要评估Timer处理超时是否经常发生,如果是很少出现,那么可以用lock(Object)的方法来防止重入
如果这种情况经常出现呢?那就要用另外的方法来防止重入了
我们可以设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃执行
static int inTimer = 0;
public static void threadTimerCallback(Object obj)
{
if ( inTiemr == 0 )
{
inTimer = 1;
Console.WriteLine( "Time:{0}, \tThread ID:{1} ", DateTime.Now, Thread.CurrentThread.GetHashCode());
Thread.Sleep(2000);
inTimer = 0;
}
}
但是在多线程下给inTimer赋值不够安全,还好Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法
static int inTimer = 0;
public static void threadTimerCallback(Object obj)
{
if ( Interlocked.Exchange(ref inTimer, 1) == 0 )
{
Console.WriteLine( "Time:{0}, \tThread ID:{1} ", DateTime.Now, Thread.CurrentThread.GetHashCode());
Thread.Sleep(250);
Interlocked.Exchange(ref inTimer, 0);
}
}
正确的选择使用.NET中的三个Timer
Timer这个类在.NET的类库中有三个:
1)System.Threading.Timer
是一个使用回调方法的计时器,而且由线程池线程服务,简单且对资源要求不高。
2)System.Windows.Forms.Timer
这是一个必须和Windows窗体一起使用的Timer。
3)System.Timers.Timer
基于服务器计时器功能的Timer,根据服务器系统时间进行运行的Timer。如果需要写Windows Services的话可以使用这个Timer来进行一
些需要在一定间隔时间进行某项操作的环境下使用。
它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可以操控此事件以提供定期处理。例如,假设您有一台关键性服务
器,必须每周 7 天、每天 24 小时都保持运行。可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。如果系统不
响应,则该服务可以尝试重新启动服务器或通知管理员。基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。服务器计时器可
以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。
using System; using System.Collections.Generic; using System.Linq; using System.ServiceProcess; using System.Text; using System.Timers; using System.Threading; using HozestService.Helper; namespace HozestService { static class Program { ///// <summary> ///// The main entry point for the application. ///// </summary> //static void Main() //{ // ServiceBase[] ServicesToRun; // ServicesToRun = new ServiceBase[] // { // new HozestService() // }; // ServiceBase.Run(ServicesToRun); //} private static System.Timers.Timer aTimer; static int inTimer = 0; public static void Main() { // Normally, the timer is declared at the class level, // so that it stays in scope as long as it is needed. // If the timer is declared in a long-running method, // KeepAlive must be used to prevent the JIT compiler // from allowing aggressive garbage collection to occur // before the method ends. You can experiment with this // by commenting out the class-level declaration and // uncommenting the declaration below; then uncomment // the GC.KeepAlive(aTimer) at the end of the method. //System.Timers.Timer aTimer; // Create a timer with a ten second interval. aTimer = new System.Timers.Timer(10000); // Hook up the Elapsed event for the timer. aTimer.Elapsed += new ElapsedEventHandler(aTimer_Elapsed); // Set the Interval to 2 seconds (2000 milliseconds). aTimer.Interval = 500; //milliseconds //aTimer.Interval = 3000; //milliseconds aTimer.Enabled = true; aTimer.AutoReset = true; //aTimer.SynchronizingObject= Console.WriteLine("Press the Enter key to exit the program."); Console.ReadLine(); // If the timer is declared in a long-running method, use // KeepAlive to prevent garbage collection from occurring // before the method ends. //GC.KeepAlive(aTimer); } // Specify what you want to happen when the Elapsed event is // raised. static void aTimer_Elapsed(object sender, ElapsedEventArgs e) { Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime.ToString()); DBHelper mDBHelper = new DBHelper(); int flag = mDBHelper.CreateData(); if (flag == 0) Console.WriteLine("插入数据失败!"); else Console.WriteLine("插入数据成功!"); if (Interlocked.Exchange(ref inTimer, 1) == 0) //以原子操作的形式,将双精度浮点数设置为指定的值并返回原始值。 { Console.WriteLine("Time:{0}, \tThread ID:{1} ", DateTime.Now, Thread.CurrentThread.GetHashCode()); Thread.Sleep(250); Interlocked.Exchange(ref inTimer, 0); } } } }