永发信息网

NET中的三种Timer的区别和用法

答案:5  悬赏:30  手机版
解决时间 2021-04-05 15:53
NET中的三种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!");
}
全部回答
.NET中有3个不同的定时器。这3个定时器分别是: //1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。 System.Windows.Forms.Timer // 2.提供以指定的时间间隔执行方法的机制。无法继承此类。 System.Threading.Timer //3.在应用程序中生成定期事件。 System.Timers.Timer
[C#].NET中的三种Timer的区别和用法   1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。 [csharp] view plain copy print? System.Windows.Forms.Timer  2.提供以指定的时间间隔执行方法的机制。无法继承此类。 [csharp] view plain copy print? System.Threading.Timer  3.在应用程序中生成定期事件。 [csharp] view plain copy print? System.Timers.Timer  这三个定时器位于不同的命名空间内,上面大概介绍了3个定时器的用途,其中第一个是只能在Windows窗体中使用的控件。在.NET1.1里面,第3个System.Timers.Timer,也是可以拖拽使用,而.NET2.0开始取消了,只能手动编写代码。而后2个没有限制制。下面通过具体的列子来看3个Timer的使用和区别,网上谈的很多,但基本都没有代码。   一、System.Windows.Forms.Timer [csharp] view plain copy print? #region System.Windows.Forms.Timer    public partial class Form1 : Form    {     public Form1() ;    InitializeComponent();    }   int num = 0;   private void Form_Timer_Tick(object sender, EventArgs e)    {     label1.Text = (++num).ToString();     Thread.Sleep(3000);    }   private void button1_Click(object sender, EventArgs e)    {     Form_Timer.Start();    }   private void button2_Click(object sender, EventArgs e)    {     Form_Timer.Stop();    }    #endregion  上面这个是一个很简单的功能,在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事件,我们前面双击这个控件时实际是增加了一行代码 [csharp] view plain copy print? this.Form_Timer.Tick += new System.EventHandler(this.Form_Timer_Tick);  这个应该明白,不明白的可以看我BLOG中有关委托和事件的文章。然后Windows将这个定时器与调用线程关联(UI线程)。当定时器触发时,Windows把一个定时器消息插入到线程消息队列中。调用线程执行一个消息泵提取消息,然后发送到回调方法中(这里的Form_Timer_Tick方法)。而这些都是单线程进行了,所以在执行回调方法时UI会假死。所以使用这个控件不宜执行计算受限或IO受限的代码,因为这样容易导致界面假死,而应该使用多线程调用的Timer。另外要注意的是这个控件时间精度不高,精度限定为 55 毫秒。我们把Interval设置为20ms,然后在start和stop方法中记录当前时,并计算出运行时间:   从上面图可以看到程序执行了7.8S也就是 7800ms,而间隔时间是20ms,也就是最后显示数字应该是390左右,但只有250,显然是不准确的,不过按MSDN说的55ms的精度,7800ms应该只执行了140多次或更少。不知道这里是不是理解有问题。 二、 System.Timers.Timer   接下来就看下另一个Timer,我们用他来改写上面的程序 [csharp] view plain copy print? #region System.Windows.Forms.Timer    public partial class Form1 : Form    {     public Form1()     InitializeComponent();    }   int num = 0;    DateTime time1 = new DateTime();    DateTime time2 = new DateTime();    //定义Timer    System.Timers.Timer Timers_Timer = new System.Timers.Timer();   private void button1_Click(object sender, EventArgs e)    {     //手动设置Timer,开始执行     Timers_Timer.Interval = 20;     Timers_Timer.Enabled = true;     Timers_Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timers_Timer_Elapsed);     time1 = DateTime.Now;    }   void Timers_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)    {     label1.Text = Convert.ToString((++num)); //显示到lable     Thread.Sleep(3000);    }   private void button2_Click(object sender, EventArgs e)    {     //停止执行     Timers_Timer.Enabled = false;     time2 = DateTime.Now;     MessageBox.Show(Convert.ToString(time2-time1));    }    #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的挂起,发现结果和第一个基本一致。 三 、System.Threading.Timer  继续用这个对象改造程序。 [csharp] view plain copy print? #region System.Windows.Forms.Timer    public partial class Form1 : Form    {     public Form1()     InitializeComponent();    }   int num = 0;    DateTime time1 = new DateTime();    DateTime time2 = new DateTime();    System.Threading.Timer Thread_Time;   private void button1_Click(object sender, EventArgs e)    {     //启动     Thread_Time = new System.Threading.Timer(Thread_Timer_Method,null,0,20);     time1 = DateTime.Now;    }   void Thread_Timer_Method(object o)    {     label1.Text = Convert.ToString((++num));     System.Threading.Thread.Sleep(3000);    }   private void button2_Click(object sender, EventArgs e)    {     //停止     Thread_Time.Dispose();     time2 = DateTime.Now;     MessageBox.Show(Convert.ToString(time2-time1));    }    #endregion       用Threading.Timer时的方法,和前面就不太相同了,所以的参数全部在构造函数中进行了设置,而且可以设置启动时间。而且没有提供start和stop方法来控制计时器。而且是以一种回调方法的方式实现,而不是通过事件来实现的。他们之间还是有区别的。   我们只有销毁掉对象来停止他。当你运行时,你会发现他和前面的Timers.Timer一样,是多线程的,主要表现在不会假死,调试运行报错。但跟让你奇怪的是,我们的代码竟然无法让她停止下来。调用了Dispose方法没有用。问题在那?然后有进行了测试,修改了间隔时间为100,200,500,1000,3000,4000。这几种情况。发现当间隔为500ms以上是基本马上就停止了。而间隔时间相对执行时间越短,继续执行的时间越长。这应该是在间隔时间小于执行时间时多个线程运行造成的。因为所有的线程不是同时停止的。间隔越短,线程越多,所以执行次数越多。   最后来看下这个对象另外一个特殊的地方。 [csharp] view plain copy print? static void Main()    {     Timer t = new Timer(Test,null,0,1000);     Console.ReadLine();    }   public static void Test(object o)    {     Console.WriteLine("nihao");     GC.Collect();    }   这段代码会输出什么结果呢?默认情况他只输出一次,就停止了。为什么呢?根据上面说的,当定义对象t,执行代码后,进行了强制垃圾回收,因为t在Main中没有其他引用,所以被回收掉了。但是如果我们吧编译器的”优化“项取消掉,在看看情况。程序进然一直在输出。为什么执行垃圾回收却没有被回收呢?因为这个禁用优化选项,t的声明周期被扩展到了方法结束。所以一直执行。   因为编译器默认是优化的,所以我们必须保证Timer对象一直被引用,而避免被垃圾回收。所以我们可以在编译器打开优化的情况下,在Main函数最后加上t=null保证回收前被引用,但你发现,这样是没用的。因为JIT编译器优化后会吧t=null直接删除,所以我们用t.Dispose(),就可以达到目的。在我们进行垃圾回收时,CLR发现t还有被引用,还没执行Dispose所以不会被回收。是以Threading.Timer有时候会出现运行一次就停止或者是销毁了还在运行的情况,而且和编译器优化也有关,所以使用时要注意。   最后看下MSDN的描述: 只要在使用 Timer,就必须保留对它的引用。对于任何托管对象,如果没有对 Timer 的引用,计时器会被垃圾回收。即使 Timer 仍处在活动状态,也会被回收。当不再需要计时器时,请使用 Dispose 方法释放计时器持有的资源。如果希望在计时器被释放时接收到信号,请使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重载。计时器已被释放后,WaitHandle 便终止。 总结:   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有关,所以对用重要的执行任务,还是建议写成服务或独立程序放在服务器上执行好了。
这3个定时器分别是: //1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 windows 窗体应用程序中,并且必须在窗口中使用。 system.windows.forms.timer // 2.提供以指定的时间间隔执行方法的机制。无法继承此类。 system.threading.timer //3.在应用程序中生成定期事件。 system.timers.timer 这三个定时器位于不同的命名空间内,上面大概介绍了3个定时器的用途,其中第一个是只能在windows窗体中使用的控件。在.net1.1里面,第3个system.timers.timer,也是可以拖拽使用,而.net2.0开始取消了,只能手动编写代码。而后2个没有限制制。下面通过具体的列子来看3个timer的使用和区别,网上谈的很多,但基本都没有代码。 一 system.windows.forms.timer #region system.windows.forms.timer public partial class form1 : form { public form1() { initializecomponent(); } int num = 0; private void form_timer_tick(object sender, eventargs e) { label1.text = (++num).tostring(); thread.sleep(3000); } private void button1_click(object sender, eventargs e) { form_timer.start(); } private void button2_click(object sender, eventargs e) { form_timer.stop(); } } #endregion 上面这个是一个很简单的功能,在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); 这个应该明白,不明白的可以看我blog中有关委托和事件的文章。然后windows将这个定时器与调用线程关联(ui线程)。当定时器触发时,windows把一个定时器消息插入到线程消息队列中。调用线程执行一个消息泵提取消息,然后发送到回调方法中(这里的form_timer_tick方法)。而这些都是单线程进行了,所以在执行回调方法时ui会假死。所以使用这个控件不宜执行计算受限或io受限的代码,因为这样容易导致界面假死,而应该使用多线程调用的timer。另外要注意的是这个控件时间精度不高,精度限定为 55 毫秒。我们把interval设置为20ms,然后在start和stop方法中记录当前时,并计算出运行时间: 从上面图可以看到程序执行了7.8s也就是 7800ms,而间隔时间是20ms,也就是最后显示数字应该是390左右,但只有250,显然是不准确的,不过按msdn说的55ms的精度,7800ms应该只执行了140多次或更少。不知道这里是不是理解有问题。 二 system.timers.timer 接下来就看下另一个timer,我们用他来改写上面的程序 #region system.windows.forms.timer public partial class form1 : form { public form1() { initializecomponent(); } int num = 0; datetime time1 = new datetime(); datetime time2 = new datetime(); //定义timer system.timers.timer timers_timer = new system.timers.timer(); private void button1_click(object sender, eventargs e) { //手动设置timer,开始执行 timers_timer.interval = 20; timers_timer.enabled = true; timers_timer.elapsed += new system.timers.elapsedeventhandler(timers_timer_elapsed); time1 = datetime.now; } void timers_timer_elapsed(object sender, system.timers.elapsedeventargs e) { label1.text = convert.tostring((++num)); //显示到lable thread.sleep(3000); } private void button2_click(object sender, eventargs e) { //停止执行 timers_timer.enabled = false; time2 = datetime.now; messagebox.show(convert.tostring(time2-time1)); } } #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的挂起,发现结果和第一个基本一致。 三 system.threading.timer 继续用这个对象改造程序。 #region system.windows.forms.timer public partial class form1 : form { public form1() { initializecomponent(); } int num = 0; datetime time1 = new datetime(); datetime time2 = new datetime(); system.threading.timer thread_time; private void button1_click(object sender, eventargs e) { //启动 thread_time = new system.threading.timer(thread_timer_method,null,0,20); time1 = datetime.now; } void thread_timer_method(object o) { label1.text = convert.tostring((++num)); system.threading.thread.sleep(3000); } private void button2_click(object sender, eventargs e) { //停止 thread_time.dispose(); time2 = datetime.now; messagebox.show(convert.tostring(time2-time1)); } } #endregion
//1.实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。 [csharp] view plain copy print? System.Windows.Forms.Timer // 2.提供以指定的时间间隔执行方法的机制。无法继承此类。 [csharp] view plain copy print? System.Threading.Timer //3.在应用程序中生成定期事件。 [csharp] view plain copy print? System.Timers.Timer 这三个定时器位于不同的命名空间内,上面大概介绍了3个定时器的用途,其中第一个是只能在Windows窗体中使用的控件。在.NET1.1里面,第3个System.Timers.Timer,也是可以拖拽使用,而.NET2.0开始取消了,只能手动编写代码。而后2个没有限制制。下面通过具体的列子来看3个Timer的使用和区别,网上谈的很多,但基本都没有代码。 一 System.Windows.Forms.Timer [csharp] view plain copy print? #region System.Windows.Forms.Timer public partial class Form1 : Form { public Form1() ; InitializeComponent(); } int num = 0; private void Form_Timer_Tick(object sender, EventArgs e) { label1.Text = (++num).ToString(); Thread.Sleep(3000); } private void button1_Click(object sender, EventArgs e) { Form_Timer.Start(); } private void button2_Click(object sender, EventArgs e) { Form_Timer.Stop(); } #endregion 上面这个是一个很简单的功能,在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事件,我们前面双击这个控件时实际是增加了一行代码 [csharp] view plain copy print? this.Form_Timer.Tick += new System.EventHandler(this.Form_Timer_Tick); 这个应该明白,不明白的可以看我BLOG中有关委托和事件的文章。然后Windows将这个定时器与调用线程关联(UI线程)。当定时器触发时,Windows把一个定时器消息插入到线程消息队列中。调用线程执行一个消息泵提取消息,然后发送到回调方法中(这里的Form_Timer_Tick方法)。而这些都是单线程进行了,所以在执行回调方法时UI会假死。所以使用这个控件不宜执行计算受限或IO受限的代码,因为这样容易导致界面假死,而应该使用多线程调用的Timer。另外要注意的是这个控件时间精度不高,精度限定为 55 毫秒。我们把Interval设置为20ms,然后在start和stop方法中记录当前时,并计算出运行时间: 从上面图可以看到程序执行了7.8S也就是 7800ms,而间隔时间是20ms,也就是最后显示数字应该是390左右,但只有250,显然是不准确的,不过按MSDN说的55ms的精度,7800ms应该只执行了140多次或更少。不知道这里是不是理解有问题。 二 System.Timers.Timer 接下来就看下另一个Timer,我们用他来改写上面的程序 [csharp] view plain copy print? #region System.Windows.Forms.Timer public partial class Form1 : Form { public Form1() InitializeComponent(); } int num = 0; DateTime time1 = new DateTime(); DateTime time2 = new DateTime(); //定义Timer System.Timers.Timer Timers_Timer = new System.Timers.Timer(); private void button1_Click(object sender, EventArgs e) { //手动设置Timer,开始执行 Timers_Timer.Interval = 20; Timers_Timer.Enabled = true; Timers_Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timers_Timer_Elapsed); time1 = DateTime.Now; } void Timers_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { label1.Text = Convert.ToString((++num)); //显示到lable Thread.Sleep(3000); } private void button2_Click(object sender, EventArgs e) { //停止执行 Timers_Timer.Enabled = false; time2 = DateTime.Now; MessageBox.Show(Convert.ToString(time2-time1)); } #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的挂起,发现结果和第一个基本一致。 三 System.Threading.Timer 继续用这个对象改造程序。 [csharp] view plain copy print? #region System.Windows.Forms.Timer public partial class Form1 : Form { public Form1() InitializeComponent(); } int num = 0; DateTime time1 = new DateTime(); DateTime time2 = new DateTime(); System.Threading.Timer Thread_Time; private void button1_Click(object sender, EventArgs e) { //启动 Thread_Time = new System.Threading.Timer(Thread_Timer_Method,null,0,20); time1 = DateTime.Now; } void Thread_Timer_Method(object o) { label1.Text = Convert.ToString((++num)); System.Threading.Thread.Sleep(3000); } private void button2_Click(object sender, EventArgs e) { //停止 Thread_Time.Dispose(); time2 = DateTime.Now; MessageBox.Show(Convert.ToString(time2-time1)); } #endregion 用Threading.Timer时的方法,和前面就不太相同了,所以的参数全部在构造函数中进行了设置,而且可以设置启动时间。而且没有提供start和stop方法来控制计时器。而且是以一种回调方法的方式实现,而不是通过事件来实现的。他们之间还是有区别的。 我们只有销毁掉对象来停止他。当你运行时,你会发现他和前面的Timers.Timer一样,是多线程的,主要表现在不会假死,调试运行报错。但跟让你奇怪的是,我们的代码竟然无法让她停止下来。调用了Dispose方法没有用。问题在那?然后有进行了测试,修改了间隔时间为100,200,500,1000,3000,4000。这几种情况。发现当间隔为500ms以上是基本马上就停止了。而间隔时间相对执行时间越短,继续执行的时间越长。这应该是在间隔时间小于执行时间时多个线程运行造成的。因为所有的线程不是同时停止的。间隔越短,线程越多,所以执行次数越多。 最后来看下这个对象另外一个特殊的地方。 [csharp] view plain copy print? static void Main() { Timer t = new Timer(Test,null,0,1000); Console.ReadLine(); } public static void Test(object o) { Console.WriteLine("nihao"); GC.Collect(); } 根据上面说的,当定义对象t,执行代码后,进行了强制垃圾回收,因为t在Main中没有其他引用,所以被回收掉了。但是如果我们吧编译器的”优化“项取消掉,在看看情况。程序进然一直在输出。这个禁用优化选项,t的声明周期被扩展到了方法结束。所以一直执行。 因为编译器默认是优化的,所以必须保证Timer对象一直被引用,而避免被垃圾回收。所以可以在编译器打开优化的情况下,在Main函数最后加上t=null保证回收前被引用,但你发现,这样是没用的。因为JIT编译器优化后会吧t=null直接删除,所以用t.Dispose(),就可以达到目的。在我们进行垃圾回收时,CLR发现t还有被引用,还没执行Dispose所以不会被回收。是以Threading.Timer有时候会出现运行一次就停止或者是销毁了还在运行的情况,而且和编译器优化也有关,所以使用时要注意。 最后看下MSDN的描述: 只要在使用 Timer,就必须保留对它的引用。对于任何托管对象,如果没有对 Timer 的引用,计时器会被垃圾回收。即使 Timer 仍处在活动状态,也会被回收。当不再需要计时器时,请使用 Dispose 方法释放计时器持有的资源。如果希望在计时器被释放时接收到信号,请使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重载。计时器已被释放后,WaitHandle 便终止。 总结: 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有关,所以对用重要的执行任务,还是建议写成服务或独立程序放在服务器上执行好了。
我要举报
如以上问答信息为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
大家都在看
鲜芋仙的芋头块怎么做,鲜芋仙的芋头怎么煮出
单选题All of the things th
我之前的环保标志上是国3的。今天审车成国2的
0.125^(-1/3) 运算的全过程 很详细的解法
相亲见面后男孩说见女方父母,结果第二天又说
选出没有错别字的一组A.赌博 通宵达旦 山青水
日本核辐射对中国大陆有没有影响,蔬菜水果检
廊坊市今天是哪里着的大火。 浓烟特别大
问:嘴角这儿长了一个痘痘,快疼死了,我想挤
东西掉入电梯一般掉到哪了
我家有会飞的蟑螂啊。。。。好恐怖、要怎么把
我什么宽带连接换了一个主机箱就连不上了
我的显卡驱动光盘显示的是什么意思?
农业宣传口号,中国农业银行广告语
感冒牙痛怎么回事
推荐资讯
如何更改百度糯米上绑定的手机号
花千骨番外的结局,是不是悲剧
改动板式换热器接管口径会影响换热吗
沐源川隧道地址有知道的么?有点事想过去
机动车三年没有年检车牌号会被注销吗?
野味有哪些,江西哪里野味多便宜?
在完美世界中天人族和石昊什么时候和好的
单选题罗斯福在实施新政时,“拒绝了任何彻底
有什么像织田信奈的野望一样的穿越到异世的后
简单的豆角素馅包子的做法怎么做好吃又简
单选题“若b2-4ac<0,则ax2+bx+c=0没有实根
正常番茄是多少倍体,无籽番茄是多少倍体?
正方形一边上任一点到这个正方形两条对角线的
阴历怎么看 ?