当前位置:编程学习 > asp >>

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

 

最近正好做一个WEB中定期执行的程序,而.NET中有3个不同的定时器。所以正好研究研究。这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

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

 

我们只有销毁掉对象来停止他。当你运行时,你会发现他和前面的Timers.Timer一样,是多线程的,主要表现在不会假死,调试运行报错。但跟让你

补充:Web开发 , ASP.Net ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,