分析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的:
ContractedBlock.gif ExpandedBlockStart.gif Code
public partial class _Default : System.Web.UI.Page
{
    
static System.Windows.Forms.Timer timer;
    
static int X = 0;
    
static _Default()
    {
        Thread thread 
= new Thread(delegate()
        {
            System.Windows.Forms.Form form 
= new System.Windows.Forms.Form();
            form.Load 
+= delegate
            {
                timer 
= new System.Windows.Forms.Timer();
                timer.Interval 
= 1000;
                timer.Tick 
+= delegate
                {
                    X
++;
                };
                timer.Start();
            };
            System.Windows.Forms.Application.Run(form);
        });
        thread.Start();
    }
    
protected void Page_Load(object sender, EventArgs e)
    {
        Label1.Text 
= X.ToString();
    }
我可没推荐你真的这么用!

转载于:https://www.cnblogs.com/zhucai/archive/2009/10/16/timer.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值