分析System.Windows.Forms.Timer和另两个Timer的重入行为

不熟悉Timer的可以先看看MSDN的描述:计时器  服务器计时器、Windows 计时器和线程计时器
简单来讲:
System.Windows.Forms.Timer基于Windows消息循环,用事件方式触发,在界面线程执行;
System.Timers.Timer更精确,用事件方式触发,在线程池执行;
System.Threading.Timer设计为非常轻量级,用回调函数引发,在线程池执行。
概念约定:在一次执行未结束时,到了第二次执行的时间,如果第二次不等第一次结束便马上执行,则称为重入
由于使用多线程,System.Timers.Timer和System.Threading.Timer是会重入的。
那么可以想象:
1.计算时间的线程应该不是执行用户函数的线程;
2.执行线程不会总是同一个线程。
下面重点讨论System.Windows.Forms.Timer(下面简称WinTimer)。
由于WinTimer是基于Windows消息循环,显然是为WinForm程序准备,所以WinTimer的引发都在界面线程。
那么理论上讲,如果界面线程阻塞,则不可能收到WinTimer的事件。

看代码:

public class Form1 : Form
{
    Timer timer 
= new Timer();
    
int num = 0;

    
public Form1()
    {
        timer.Interval 
= 100;
        timer.Tick 
+= new EventHandler(timer_Tick);
        timer.Start();
    }

    
void timer_Tick(object sender, EventArgs e)
    {
        num
++;
        
switch (num)
        {
            
case 1// 第一次
                System.Threading.Thread.Sleep(3000);
                Console.Write(num);
                
break;

            
case 2// 第二次
                timer.Stop();
                Console.Write(num);
                
break;
        }
    }
}
是的,输出窗口(“调试”->“窗口”->“输出”)显示为:12
那么,下面的代码,你认为会怎么输出?

public class Form1 : Form
{
    Timer timer 
= new Timer();
    
int num = 0;

    
public Form1()
    {
        timer.Interval 
= 100;
        timer.Tick 
+= new EventHandler(timer_Tick);
        timer.Start();
    }

    
void timer_Tick(object sender, EventArgs e)
    {
        num
++;
        
int temp = num;
        
switch (num)
        {
            
case 1// 第一次
                MessageBox.Show(num.ToString());
                Console.Write(temp);
                
break;

            
case 2// 第二次
                timer.Stop();
                Console.Write(temp);
                
break;
        }
    }
}
输出:12?
不见得!去运行它,你会发现,当你看到弹出窗口还没来得及点击的时候,输出窗口就已经输出2
当你点击弹出窗口的“确定”按钮,输出窗口会输出1。
也就是说,顺序是:21!
于是我们惊讶的发现:Thread.Sleep可以阻止WinTimer的事件再次触发,而MessageBox.Show却无法阻止!
万事皆有其因!对程序来讲尤其如此。

分析:
之所以Thread.Sleep阻止了WinTimer,是因为它阻塞了界面线程。
而MessageBox.Show呢?它也阻塞了界面啊!
从程序运行过程分析,MessageBox.Show必须要等我们点击了“确定”按钮才会执行下一句代码,通过逐句跟踪我们发现程序确实停在了这里。
而case 2里的Console.Write为什么在这中间还能得到运行呢?
将case 2里的Console.Write行代码设置断点。
运行。
断点被命中的时候,打开调用堆栈窗口(“调试”->“窗口”->“调用堆栈”),你会看到两个timer_Tick方法,双击下面那个timer_Tick,你会看到:

原来第二次timer事件在Message.Show方法里!(有兴趣的可以打开“选项”->“调试”->勾掉“启用'仅我的代码'”,看内部方法)
真相大白:Message.Show虽然阻塞了外面的代码,但它里面仍然有消息循环,于是Timer的事件得以触发。
结论:当没有消息循环或被阻塞的时候,WinTimer的事件是触发不了的,但此线程只要有消息循环,WinTimer就可以正常触发。
Thread.Sleep以及任何无消息循环的代码,都可以阻塞消息循环,也便阻塞了WinTimer;
而Message.Show,Form.ShowDialog等,它们虽然阻塞了外面的消息循环,但它们里面也有消息循环,所以不会阻塞WinTimer。

附:既然System.Windows.Forms.Timer是基于消息循环,那么当然只要有消息循环就可以使用System.Windows.Forms.Timer,
那么WebForm添加System.Windows.Forms引用,也是可以这样使用System.Windows.Forms.Timer的:
Code
我可没推荐你真的这么用!

posted @ 2009-10-16 14:05  朱才  阅读(3341)  评论(3编辑  收藏  举报