System.Timers 防止事件处理程序的可重入问题解决方案

问题: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 不是 nullElapsed 在 Dispose 或 Stop 方法调用后或 Enabled 属性设置为 false 后,事件可以发生,这是因为引出 Elapsed 事件的信号总是排队等待执行线程池线程。 解决此争用条件的一种方法是:设置一个标志,通知 Elapsed 事件的事件处理程序忽略随后的事件。

如果您使用 Timer 与用户界面元素如窗体或控件,没有放置计时器的该用户界面元素,指定包含 Timer 窗体或控件到 SynchronizingObject 属性,以使该事件封送到用户界面线程。

Timer 在运行时是不可见的。

有关 Timer 的实例的初始属性值列表,请参见 Timer 构造函数。

注意 注意

应用到此类型或成员的 HostProtectionAttribute 特性具有以下 Resources 属性值:Synchronization | ExternalThreadingHostProtectionAttribute 不影响桌面应用程序(桌面应用程序一般通过双击图标、键入命令或在浏览器中输入 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);
            } 
        }
    }
}

 

 

posted @ 2012-10-24 13:55  xust  阅读(750)  评论(0编辑  收藏  举报