.NET WinForm中的三种Timer(计时器)的区别和用法

  • .NET中的三种Timer(计时器)的区别和用法
  • 卡卡 发表于 2014/12/5 13:02:00 | 分类标签: Timer 计时器 ASP.NET
  • NET中有3个不同的定时器。这3个定时器分别是:

    1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。
    System.Windows.Forms.Timer

    2.提供以指定的时间间隔执行方法的机制。无法继承此类。
    System.Threading.Timer

    /3.在应用程序中生成定期事件。
    System.Timers.Timer

    一 System.Windows.Forms.Timer

    1. public partial class Form1 : Form  
    2. {  
    3. public Form1()  
    4. {  
    5. InitializeComponent();  
    6. }  
    7.  
    8. int num = 0;  
    9.  
    10. privatevoid Form_Timer_Tick(object sender, EventArgs e)  
    11. {  
    12. label1.Text = (++num).ToString();  
    13. Thread.Sleep(3000);  
    14. }  
    15.  
    16. privatevoid button1_Click(object sender, EventArgs e)  
    17. {  
    18. Form_Timer.Start();  
    19. }  
    20.  
    21. privatevoid button2_Click(object sender, EventArgs e)  
    22. {  
    23. Form_Timer.Stop();  
    24. }  
    25. }  

    上面这个是一个很简单的功能,在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);
    然后Windows将这个定时器与调用线程关联(UI线程)。当定时器触发时,Windows把一个定时器消息插入到线程消息队列中。调用线程执行一个消息泵提取消息,然后发送到回调方法中(这里的Form_Timer_Tick方法)。而这些都是单线程进行了,所以在执行回调方法时UI会假死。所以使用这个控件不宜执行计算受限或IO受限的代码,因为这样容易导致界面假死,而应该使用多线程调用的Timer。另外要注意的是这个控件时间精度不高,精度限定为 55 毫秒。我们把Interval设置20ms,然后在start和stop方法中记录当前时,并计算出运行时间:

    二 System.Timers.Timer


    接下来就看下另一个Timer,我们用他来改写上面的程序

    1. #region System.Windows.Forms.Timer  
    2. public partial class Form1 : Form  
    3. {  
    4. public Form1()  
    5. {  
    6. InitializeComponent();  
    7. }  
    8.  
    9. int num = 0;  
    10. DateTime time1 =new DateTime();  
    11. DateTime time2 =new DateTime();  
    12. //定义Timer 
    13. System.Timers.Timer Timers_Timer =new System.Timers.Timer();  
    14.  
    15. privatevoid button1_Click(object sender, EventArgs e)  
    16. {  
    17. //手动设置Timer,开始执行 
    18. Timers_Timer.Interval =20;  
    19. Timers_Timer.Enabled =true;  
    20. Timers_Timer.Elapsed +=new System.Timers.ElapsedEventHandler(Timers_Timer_Elapsed);  
    21. time1 = DateTime.Now;  
    22. }  
    23.  
    24. void Timers_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)  
    25. {  
    26. label1.Text = Convert.ToString((++num));//显示到lable 
    27. Thread.Sleep(3000);  
    28. }  
    29.  
    30. privatevoid button2_Click(object sender, EventArgs e)  
    31. {  
    32. //停止执行 
    33. Timers_Timer.Enabled =false;  
    34. time2 = DateTime.Now;  
    35. MessageBox.Show(Convert.ToString(time2-time1));  
    36. }  
    37. }  
    38. #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就编程单线程调用了,就基本和第一个完全一样了。
    Timer 是为在多线程环境中用于辅助线程而设计的。服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程上将会再次引发此事件。因此,事件处理程序应当是可重入的。


    另外和前面不同的现象是每次加1后并没有停止3秒在显示。而是继续显示,只是速度稍慢。因为我们设置间隔为20ms,而执行时间为3s,所以会在20ms后在另一个线程中继续执行,而当前线程被挂起而已。关于计时器的精度,取消3s的挂起,发现结果和第一个基本一致。
    写成如下就可以正常在lable中显示递增的数字,2s增加一次。

    三 System.Threading.Timer


    继续用这个对象改造程序。

    1. public partial class Form1 : Form  
    2. {  
    3. public Form1()  
    4. {  
    5. InitializeComponent();  
    6. }  
    7.  
    8. int num = 0;  
    9. DateTime time1 =new DateTime();  
    10. DateTime time2 =new DateTime();  
    11. System.Threading.Timer Thread_Time;  
    12.  
    13. privatevoid button1_Click(object sender, EventArgs e)  
    14. {  
    15. //启动 
    16. Thread_Time =new System.Threading.Timer(Thread_Timer_Method,null,0,20);  
    17. time1 = DateTime.Now;  
    18.  
    19. }  
    20.  
    21. void Thread_Timer_Method(object o)  
    22. {  
    23. label1.Text = Convert.ToString((++num));  
    24. System.Threading.Thread.Sleep(3000);  
    25. }  
    26.  
    27. privatevoid button2_Click(object sender, EventArgs e)  
    28. {  
    29. //停止 
    30. Thread_Time.Dispose();  
    31. time2 = DateTime.Now;  
    32. MessageBox.Show(Convert.ToString(time2-time1));  
    33. }  
    34. }  

    用Threading.Timer时的方法,和前面就不太相同了,所以的参数全部在构造函数中进行了设置,而且可以设置启动时间。而且没有提供start和stop方法来控制计时器。而且是以一种回调方法的方式实现,而不是通过事件来实现的。他们之间还是有区别的。


    我们只有销毁掉对象来停止他。当你运行时,你会发现他和前面的Timers.Timer一样,是多线程的,主要表现在不会假死,调试运行报错。但跟让你奇怪的是,我们的代码竟然无法让她停止下来。调用了Dispose方法没有用。问题在那?然后有进行了测试,修改了间隔时间为100,200,500,1000,3000,4000。这几种情况。发现当间隔为500ms以上是基本马上就停止了。而间隔时间相对执行时间越短,继续执行的时间越长。这应该是在间隔时间小于执行时间时多个线程运行造成的。因为所有的线程不是同时停止的。间隔越短,线程越多,所以执行次数越多。
    最后来看下这个对象另外一个特殊的地方。

    1. staticvoid Main()  
    2. {  
    3. Timer t =new Timer(Test,null,0,1000);  
    4. Console.ReadLine();  
    5. }  
    6.  
    7. publicstaticvoid Test(object o)  
    8. {  
    9. Console.WriteLine("nihao");  
    10. GC.Collect();  
    11. }  


    这段代码会输出什么结果呢?默认情况他只输出一次,就停止了。为什么呢?根据上面说的,当定义对象t,执行代码后,进行了强制垃圾回收,因为t在Main中没有其他引用,所以被回收掉了。但是如果我们吧编译器的”优化“项取消掉,在看看情况。程序进然一直在输出。为什么执行垃圾回收却没有被回收呢?因为这个禁用优化选项,t的声明周期被扩展到了方法结束。所以一直执行。


    因为编译器默认是优化的,所以我们必须保证Timer对象一直被引用,而避免被垃圾回收。所以我们可以在编译器打开优化的情况下,在Main函数最后加上t=null保证回收前被引用,但你发现,这样是没用的。因为JIT编译器优化后会吧t=null直接删除,所以我们用t.Dispose(),就可以达到目的。在我们进行垃圾回收时,CLR发现t还有被引用,还没执行Dispose所以不会被回收。是以Threading.Timer有时候会出现运行一次就停止或者是销毁了还在运行的情况,而且和编译器优化也有关,所以使用时要注意。
    另一个例子:

    1. using System; 
    2. using System.Threading; 
    3.  
    4. namespace CallBacks 
    5.    class Program 
    6.     { 
    7.        private string message; 
    8.        privatestatic Timer timer; 
    9.        privatestatic bool complete; 
    10.  
    11.        staticvoid Main(string[] args) 
    12.         { 
    13.             Program p =new Program(); 
    14.             Thread workerThread =new Thread(p.DoSomeWork); 
    15.             workerThread.Start(); 
    16.  
    17.            //create timer with callback 
    18.             TimerCallback timerCallBack = 
    19.                new TimerCallback(p.GetState); 
    20.             timer =new Timer(timerCallBack,null,  
    21.                 TimeSpan.Zero, TimeSpan.FromSeconds(2)); 
    22.  
    23.            //wait for worker to complete 
    24.            do 
    25.             { 
    26.                //simply wait, do nothing 
    27.             }while (!complete); 
    28.  
    29.             Console.WriteLine("exiting main thread"); 
    30.             Console.ReadLine(); 
    31.         } 
    32.  
    33.        publicvoid GetState(Object state) 
    34.         { 
    35.            //not done so return 
    36.            if (message == string.Empty)return
    37.             Console.WriteLine("Worker is {0}", message); 
    38.            //is other thread completed yet, if so signal main 
    39.            //thread to stop waiting 
    40.            if (message =="Completed"
    41.             { 
    42.                 timer.Dispose(); 
    43.                 complete =true
    44.             } 
    45.         } 
    46.  
    47.        publicvoid DoSomeWork() 
    48.         { 
    49.             message ="processing"
    50.            //simulate doing some work 
    51.             Thread.Sleep(3000);  
    52.             message ="Completed"
    53.         } 
    54.     } 
    55. }  

    总结:
    System.Threading.Timer 是一个简单的轻量计时器,它使用回调方法并由线程池线程提供服务。不建议将其用于 Windows 窗体,因为其回调不在用户界面线程上进行。System.Windows.Forms.Timer 是用于 Windows 窗体的更佳选择。要获取基于服务器的计时器功能,可以考虑使用 System.Timers.Timer,它可以引发事件并具有其他功能。


    在《CLR Via C#》中讲多线程时有提到这3个计时器,但作者说System.Timers.Timer是对System.Threading.Timer的报装,不推荐使用,但是在我的WEB项目中的Application_Start中我还是使用的这个而不是Threading.Timer,因为使用Threading.Timer时只执行了一次就不在执行了。


    对于计时器在B/S结构中的使用就复杂一些,一般我们把计时器放在Application_OnStart中,这样全局维护一个计时器,可以进行定期备份数据库,定期维护用户等操作,而且方法写作静态的,以免被垃圾回收。而不建议在一般的aspx页面中使用,因为服务器端的定时器对用户这样意义不大,完全可以使用JS代替。而且这个页面的每个请求都可能引入一个新的定时器,导致系统崩溃。另外,定时器是ASP.NET进程,IIS有关,所以对用重要的执行任务,还是建议写成服务或独立程序放在服务器上执行好了。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页