zoukankan      html  css  js  c++  java
  • 浅谈.NET中可用的定时器和计时器【上篇】

    .net中的计时问题可能每个人都会遇到,但是很少有人系统的总结,Baidu了下,无果,故写作本文。

    本文旨在总结.net中可用的各种计时方法,介绍的是DebugLZQ知道的几种.net中的定时、计时方法。并进行了我所知道的分析(重点)和简单使用,并不涉及更深层次的讨论。

    进入正题:

      定时器是系统常用的组件之一,程序员可以根据自己的需求定制一个定时器类型,也可以使用.net内建的定时器类型。在.net中一共为程序员提供了3种定时器:

    • System.Windows.Forms.Timer类型
    • System.Threading.Timer类型
    • System.Timers.Timer类型

    概括来说,这3种类型都实现了定时的功能。程序员通常需要做的是为定时器设置一个间断时间,设置定时器到时的处理方法,然后可以等待定时器不断地计时和出发时间处理,下面详细介绍下这三种类型的特点。

    1. System.Windows.Forms.Timer类型

      从这个定时器的命名空间可以看出,.net设计这个定时器的目的是为了方便程序员在Window Form中使用定时器。当一个System.Windows.Forms.Timer类被构造时,当前定时器会和当前线程进行关联。而当计时器的计时达到后,一个定时器消息将被插入到当前线程的消息队列中。当前线程逐一处理消息中的所有消息,并一一派发给各自的处理方法。这样的机制和利用工作者进程定时有很大的区别,事实上,System.Windows.Forms.Timer类型并没有涉及多线程的操作,定时器的设置、定时方法的执行都在同一个线程之上。

      这就意味着System.Windows.Forms.Timer并不能准确计时,事实上,当消息阻塞时,定时器的误差将非常大,因为定时器消息只能等待在前面的所有消息处理完后才能得到处理。但是因为System.Windows.Forms.Timer类型的定时器并不涉及多线程的操作,因此是线程安全的,不会发生回调方法重入的问题。

      简单的使用如下:

    public class Class1 {
        static System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
        static int alarmCounter = 1;
        static bool exitFlag = false;
    
        // This is the method to run when the timer is raised.
        private static void TimerEventProcessor(Object myObject,
                                                EventArgs myEventArgs) {
           myTimer.Stop();
    
           // Displays a message box asking whether to continue running the timer.
           if(MessageBox.Show("Continue running?", "Count is: " + alarmCounter, 
              MessageBoxButtons.YesNo) == DialogResult.Yes) {
              // Restarts the timer and increments the counter.
              alarmCounter +=1;
              myTimer.Enabled = true;
           }
           else {
              // Stops the timer.
              exitFlag = true;
           }
        }
    
        public static int Main() {
           /* Adds the event and the event handler for the method that will 
              process the timer event to the timer. */
           myTimer.Tick += new EventHandler(TimerEventProcessor);
    
           // Sets the timer interval to 5 seconds.
           myTimer.Interval = 5000;
           myTimer.Start();
    
           // Runs the timer, and raises the event.
           while(exitFlag == false) {
              // Processes all the events in the queue.
              Application.DoEvents();
           }
        return 0;
        }
     }
        
    •  2   System.Threading.Timer类型

      这个定时器类型的使用相对复杂,但同时它也是最优化的一个定时器类型。System.Threading.Timer的定时方法江北确定在工作者线程上执行。所有的对象有一个线程控制,当下一个定时到达时,该线程会负责在线程中获得一个新的工作者线程,用以执行相应的回调方法。

      虽然这个定时器是相对最优化的一个定时器类型,但是从其机制上来讲,其并不是线程安全的,可能会出现回调方法重入的问题。解释下方法重入,是一个有关多线程编程的概念,意思大概是:程序中,多个线程同时运行时,就可能发生同一个方法被多个进程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入会斗志数据不一致的情况,这个非常严重的bug。

      简单的使用方法如下:

    using System;
    using System.Threading;
    
    class TimerExample
    {
        static void Main()
        {
            // Create an event to signal the timeout count threshold in the
            // timer callback.
            AutoResetEvent autoEvent     = new AutoResetEvent(false);
    
            StatusChecker  statusChecker = new StatusChecker(10);
    
            // Create an inferred delegate that invokes methods for the timer.
            TimerCallback tcb = statusChecker.CheckStatus;
    
            // Create a timer that signals the delegate to invoke 
            // CheckStatus after one second, and every 1/4 second 
            // thereafter.
            Console.WriteLine("{0} Creating timer.\n", 
                DateTime.Now.ToString("h:mm:ss.fff"));
            Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250);
    
            // When autoEvent signals, change the period to every
            // 1/2 second.
            autoEvent.WaitOne(5000, false);
            stateTimer.Change(0, 500);
            Console.WriteLine("\nChanging period.\n");
    
            // When autoEvent signals the second time, dispose of 
            // the timer.
            autoEvent.WaitOne(5000, false);
            stateTimer.Dispose();
            Console.WriteLine("\nDestroying timer.");
        }
    }
    
    class StatusChecker
    {
        private int invokeCount;
        private int  maxCount;
    
        public StatusChecker(int count)
        {
            invokeCount  = 0;
            maxCount = count;
        }
    
        // This method is called by the timer delegate.
        public void CheckStatus(Object stateInfo)
        {
            AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
            Console.WriteLine("{0} Checking status {1,2}.", 
                DateTime.Now.ToString("h:mm:ss.fff"), 
                (++invokeCount).ToString());
    
            if(invokeCount == maxCount)
            {
                // Reset the counter and signal Main.
                invokeCount  = 0;
                autoEvent.Set();
            }
        }
    }
    •  3   System.Timers.Timer类型

      这是一个相对较旧的类型。它和System.Threading.Timer一样,可以由工作者线程来执行回调方法,但同时它也可以在IDE环境中被拖到窗体控件上,这个时候它的行为非常类似于System.Windows.Forms.Timer类型,在消息过多时其定时并不准确。

      System.Timers.Timer可以视为System.Threading.Timer的一个包装,其类型设计相对古老,不建议使用该定时器。

      简单的使用如下:

    using System;
    using System.Timers;
    
    public class Timer1
    {
        private static System.Timers.Timer aTimer;
    
        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(OnTimedEvent);
    
            // Set the Interval to 2 seconds (2000 milliseconds).
            aTimer.Interval = 2000;
            aTimer.Enabled = true;
    
            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.
        private static void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
        }
    }
    
    /* This code example produces output similar to the following:
    
    Press the Enter key to exit the program.
    The Elapsed event was raised at 5/20/2007 8:42:27 PM
    The Elapsed event was raised at 5/20/2007 8:42:29 PM
    The Elapsed event was raised at 5/20/2007 8:42:31 PM
    ...
     */

      4   System.Windows.Threading.DispatcherTimer类型(补充)

      System.Windows.Threading.DispatcherTimer集成到按指定时间间隔和指定优先级处理的 Dispatcher 队列中的计时器。因为 DispatcherTimer 操作与其他操作一样被放置到 Dispatcher 队列中,何时执行 DispatcherTimer 操作取决于队列中的其他作业及其优先级,因此它不能保证会正好在时间间隔发生时执行计时器,但能够保证不会在时间间隔发生之前执行计时器。

      如果 System.Timers.Timer 用于 WPF 应用程序,则值得注意的是 System.Timers.Timer 运行于不同于user interface (UI) 线程的其他线程上。 为了访问user interface (UI) 线程上的对象,需要使用 InvokeBeginInvoke 将操作发布到user interface (UI) 线程的 Dispatcher 上。

      简单实用示例:

    //  DispatcherTimer setup
    dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
    dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
    dispatcherTimer.Interval = new TimeSpan(0,0,1);
    dispatcherTimer.Start();

     这个参考DebugLZQ博文:WPF整理-实现一个Clock小工具

    Update: Timer的Tick Handler(dispatcherTimer_Tick等)第一次执行的时机是:Start以后,在第一个Interval到达时(Start时并不执行)。

    Tip:在实际开发中,应尽量避免使用Timer!!! Timer往往和Work around联系到一起,而work around并不是Solution。

    对.net的定时器的总结就这么多。下篇将总结下.net中可用的计时器。请期待~

    【请点击下面的“绿色通道”---“关注DebugLZQ”,共同交流进步~】 

  • 相关阅读:
    re.match() and re.search()
    libxml2 使用教程【转】
    Spring PropertyPlaceholderConfigurer类载入外部配置
    Spring PropertyPlaceholderConfigurer类载入外部配置
    Spring PropertyPlaceholderConfigurer类载入外部配置
    Spring PropertyPlaceholderConfigurer类载入外部配置
    前缀和、前缀积
    前缀和、前缀积
    前缀和、前缀积
    前缀和、前缀积
  • 原文地址:https://www.cnblogs.com/DebugLZQ/p/2623669.html
Copyright © 2011-2022 走看看