昨天看到一本书中介绍了关于WM_TIMER消息的工作原理,结合我自己的理解阐述如下:
具体如何根据硬件的时钟滴答来发送TIMER消息,我不太了解,我只是说一下WINDOWS
对它的处理,WM_TIMER在消息队列中的优先级基本是最低的,并且在一个队列中同时只能有
一个timer消息。比如说你的timer每秒钟触发一次,并且如果这个timer事件的处理超过一秒钟的话,
例如3秒,那么在这三秒内应该触发三个timer消息,但是消息队列会忽略后两个timer消息,
只保留第一个,那么最后的结果好像是你的timer三秒钟才触发一次(这里假设你的程序中没有其他事件要处理,只有一个timer事件)。
举个例子,你在窗口中放一个timer组件和一个memo组件,timer一秒触发一次,然后在timer事件中加入这段代码:
sleep(1250);
memo1.lines.add(datetimetostr(now()));
那么你会发现memo中是按照这个规律显示时间:
2005-3-24 21:22:31
2005-3-24 21:22:32
2005-3-24 21:22:33
2005-3-24 21:22:35
2005-3-24 21:22:36
2005-3-24 21:22:37
2005-3-24 21:22:38
2005-3-24 21:22:40
2005-3-24 21:22:41
2005-3-24 21:22:42
2005-3-24 21:22:43
2005-3-24 21:22:45
2005-3-24 21:22:46
2005-3-24 21:22:47
2005-3-24 21:22:48
2005-3-24 21:22:50
2005-3-24 21:22:51
2005-3-24 21:22:52
2005-3-24 21:22:53
2005-3-24 21:22:55
注意一下后面的秒,有一定的规律,如果把时间精确到毫秒的话,那么可以看出其实是每隔
1.25秒显示一次时间。
我有一个疑问,如果你的timer事件的处理需要一定的时间,那么在段时间内,其他新加入队列的消息
就得不到及时的处理,程序看起来象死机一样,此时该怎办??调用processmessages??

解决方案 »

  1.   

    但是你的TIMER是1秒一次,所以造成了这个延时显示
      

  2.   

    我的意思是如果你想很精确的每一秒钟执行一个事件是不可能的,timer事件并不是异步执行的
      

  3.   

    關于timer , WM_TIMER的,在 delphi 深度歷險面一書中,說得很清楚
      

  4.   

    >>如果你的timer事件的处理需要一定的时间,那么在段时间内,其他新加入队列的消息
    就得不到及时的处理,程序看起来象死机一样,此时该怎办??调用processmessages??调用processmessages是一个办法
    但是如果处理时间>3秒,我想使用TImer事件就不太好了  还是提交给另一个线程吧
      

  5.   

    因为sleep(1250);的中断等级高于Timer,所以先中断1.25秒再计时,出现这样的问题是当然的。
      

  6.   

    你可以参考大富翁论坛
    关于:"时钟控件定时产生线程操作数据库,shi线程资源释放的问题"
    该问题的URL是: http://www.delphibbs.com/delphibbs/dispq.asp?LID=673905
    可能会有点启发。
      

  7.   

    〉〉我的意思是如果你想很精确的每一秒钟执行一个事件是不可能的,timer事件并不是异步执行的
    为啥不sleep(5250);?这样更容易看出问题
      

  8.   

    timer在消息队列中的等级是最低的,即便不用sleep,使用其他方法使该timer事件执行时间超过1秒,或者程序中处理其他的消息超过一秒,那么结果也会是1秒钟的timer不会真正的一秒触发一次。
    是不是这样??
      

  9.   

    还有一个问题,timer的最低分辨率是大概10毫秒,那么如果你的timer间隔是1毫秒的话,也会10毫秒触发一次,有没有其他的办法使计时器能够更加精确??
      

  10.   

    最新发现,timer事件的触发间隔是完全无规律的,甚至两个timer事件的时间间隔比你定义的还要短,有
    程序为证:
    定义两个全局变量:    
    iInterval: integer;
    dtBegin: TDateTime;timer事件如下:
    procedure TForm1.Timer1Timer(Sender: TObject);
    var
      boolFlag: boolean;
      dtCur: TDateTime;
      h,m,s,ms: Word;
    begin
      boolFlag := true;
      while boolFlag do
      begin
        iInterval := iInterVal + 1;
        dtCur := Now();
        DecodeTime((dtCur - dtBegin),h,m,s,ms);
        Memo1.Lines.Add(Inttostr(s*1000 + ms));
        dtBegin := dtCur;
        if iInterval mod 2 = 0 then
          Sleep(1600)
        else
          Sleep(200);
        boolFlag := false;
      end;
    end;结果的规律如下:
    1002
    1602
    401
    1602
    401
    1602
    400
    1603
    400
    1603
    400
    1603
    400
    1602
    401
    1602
    401
    1602
    401
    1602
    401
    1602这表明我定义1秒触发一次的timer,由于我的timer事件执行时间过长,导致两个timer事件之间间隔
    小于1秒
      

  11.   

    >>timer在消息队列中的等级是最低的,即便不用sleep,使用其他方法使该timer事件执行时间超过1秒,或者程序中处理其他的消息超过一秒,那么结果也会是1秒钟的timer不会真正的一秒触发一次。
    是不是这样??
    ---肯定的啊>>有没有其他的办法使计时器能够更加精确??
    ---多媒体计时器>>timer事件的触发间隔是完全无规律的甚至两个timer事件的时间间隔比你定义的还要短
    ---错!
    procedure TForm1.Timer1Timer(Sender: TObject);
    ...
    begin
      boolFlag := true;
      while boolFlag do
      begin
    ...
        if iInterval mod 2 = 0 then
          Sleep(1600)//你sleep了,所以你得程序不会处理(响应)任何消息了~~~
         //但是系统还是按时给你发送了一个timer消息,可是你在睡觉,不响应它,
        //当你醒过来处理这个消息的时候,时间已经过去了
         //1600-1000=600ms,所以这次的间隔是1600ms;
        else
          Sleep(200);//你又sleep了,但是系统按时给你发送了一个timer消息,这次的是在你醒来200ms后    //后接到的,所以sleep的200ms+200ms=400ms....一切都是很有规律的啊!!!~
        boolFlag := false;
      end;
    end;
      

  12.   

    Timer是不准时的!
    想可靠就用多媒体定时器
    我在FAQ中提过的,http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=200249http://lysoft.7u7.net
      

  13.   

    TTimer 精确度在 50 - 49 ms 之间 Windows2K 比 Win98 稍准一点你要实现小于50ms的计时用TTimer是不可能实现滴 楼上多提到的多嫖体定时器应用procedure TimeCallBack(uTimerID, uMessage: UINT;dwUser, dw1, dw2: DWORD); stdcall;    //计时器回调函数
    procedure TimeCallBack(uTimerID, uMessage: UINT;dwUser, dw1, dw2: DWORD);
    begin
      PostMessage(dwUser,CM_MARQUEE,dwUser,uTimerID);
    end;procedure TSupperLED.SetHPTimer;
    var
      pTP: PTimeCaps;
    begin
        if FTimeID <> 0 then DeleteHPTimer;    GetMem(pTP,SizeOf(TTimeCaps));
        try
          timeGetDevCaps(pTP,SizeOf(TTimeCaps));        //取当前系统最高计时精度能力
          timeBeginPeriod(pTP^.wPeriodMin);             //设置计时精度为最佳
        finally
          FreeMem(pTP,SizeOf(TTimeCaps));
        end;
        {传入接收消息窗口句柄备用}
        FTimeID := timeSetEvent(1000 - FSpeed,1,@TimeCallBack,FWindowHandle,TIME_PERIODIC)  //设置高精度计时器
    end;
      

  14.   

    flyinwuhan(制怒·三思而后行)  :
    我的理解是windows会按照您设定的时间间隔准时的给你发送timer消息,但是如果您的程序处理其他的消息过长,期间windows给你的消息队列发送了数个timer消息,但是队列只保留一个,而忽略其他的。
    结果造成两个timer事件的处理间隔不到一秒,也就是说timer是准时的,但是处理事件并不准时。
    是这样吧?
      

  15.   

    >>也就是说timer是准时的,但是处理事件并不准时。
    YES
      

  16.   

    或者说,timer是不是准时的,跟你的程序没有关系,而是取决于系统,如果系统正处理繁忙的任务,就会导致timer不准时。注意,是系统处理繁忙的任务造成timer不准时,而不是你的程序处理繁忙的任务造成timer不准时>>比如说你的timer每秒钟触发一次,并且如果这个timer事件的处理超过一秒钟的话,
    例如3秒,那么在这三秒内应该触发三个timer消息,但是消息队列会忽略后两个timer消息,
    只保留第一个,那么最后的结果好像是你的timer三秒钟才触发一次这话又是错的,你的程序不会忽略消息队列后两个timer消息,并且两个timer消息是在你的程序的消息队列中的。有些人瞎说误人子弟需要自己做实验才行
      

  17.   

    我的 主程序都terminate了,但timer里的语句还要执行,导致错误。
    timer事件里需要做的事比较多,耗时长,怎么办?
      

  18.   

    flyinwuhan(制怒·三思而后行)   >>比如说你的timer每秒钟触发一次,并且如果这个timer事件的处理超过一秒钟的话,  
        例如3秒,那么在这三秒内应该触发三个timer消息,但是消息队列会忽略后两个timer消息,
        只保留第一个,那么最后的结果好像是你的timer三秒钟才触发一次    这话又是错的,你的程序不会忽略消息队列后两个timer消息,并且两个timer消息是在你的程序的
        消息队列中的  千真万确,消息队列中在任何时候只保留一个wm_timer消息(当然,如果你定义了两个timer,那就保留两个wm_timer消息。不然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:
    由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统闲下来,那你这几十个timer就一个挨着一个触发timer事件,这样的话,timer岂不是不准时的太夸张了。
      

  19.   

    注:…………然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:
    由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统…………应该是:“timer消息处理是异步的了”
      

  20.   

    niutuoshaozhe(牛拖少这) :
    不管您是正常结束程序,还是调用application.terminate结束程序,结果都是在消息队列中放
    个wm_quit消息,使fterminate属性为真,导致退出消息循环,结束程序。
    但是消息循环只有在处理完timer事件之后,才能取到wm_quit消息,应该不会出现程序退出,timer还没有处理完的情况,除非你的程序有好几个消息循环。
      

  21.   

    >>千真万确,消息队列中在任何时候只保留一个wm_timer消息...不然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统闲下来,那你这几十个timer就一个挨着一个触发timer事件,这样的话,timer岂不是不准时的太夸张了你可以试验一下:
    var
      Form1: TForm1;
      i : integer;
    implementation{$R *.dfm}procedure TForm1.FormCreate(Sender: TObject);
    begin
      i:=0;
    end;procedure TForm1.Timer1Timer(Sender: TObject);
    begin
      if i=0 then
        sleep(3000);//休息3秒  if i>3 then
      begin
        timer1.enabled:=false;
        exit;
      end;
      inc(i);
      listbox1.items.add('xxx');//你的程序会停止3秒后突然连续触发3个timer事件!!
    end;
      

  22.   

    呵呵,我的实验有问题。没有深入分析啊~~~~~~~~~~~~
    看来“消息队列中在任何时候只保留一个wm_timer消息”这句话是对的^_^
      

  23.   

    Delphi中三种延时方法及其定时精度分析     选择自 listenwind 的 Blog  
    关键字   Delphi中三种延时方法及其定时精度分析 
    出处    
     
       在Delphi中,通常可以用以下三种方法来实现程序的延时,即TTtimer控件,Sleep函数,GetTickCount函数。但是其精度是各不相同的。一、三种方法的简单介绍1)TTtimer控件  TTtimer控件的实质是调用Windows API定时函数SetTimer和KillTimer来实现的,并简化了对WM_TIMER 消息的处理过程。通过设置OnTimer事件和Interval属性,我们可以很方便的产生一些简单的定时事件。2)Sleep函数  Sleep函数用来使程序的执行延时给定的时间值。Sleep的调用形式为Sleep(milliseconds),暂停当前的进程milliseconds毫秒。Sleep的实现方法其实也是调用Windows API的Sleep函数。例如:sleep(1000);        //延迟1000毫秒Sleep会引起程序停滞,如果你延迟的时间较长的话,你的程序将不能够响应延时期间的发生的其他消息,所以程序看起来好像暂时死机。3)GetTickCount函数  在主程序中延时,为了达到延时和响应消息这两个目的,GetTickCount()构成的循环就是一种广为流传的方法。例如:procedure Delay(MSecs: Longint);
    //延时函数,MSecs单位为毫秒(千分之1秒)
    var
      FirstTickCount, Now: Longint;
    begin
      FirstTickCount := GetTickCount();
      repeat
        Application.ProcessMessages;
        Now := GetTickCount();
      until (Now - FirstTickCount >= MSecs) or (Now < FirstTickCount);
    end;二、高精度的微妙级性能计数器(high-resolution performance counter)介绍  为了比较以上方法的精度,首先需要找到一个参考的定时器。在这里,我提供了两个参考的定时器。一是用单片机每隔1.024ms产生一个实时中断RTI,作为计数器;二是选用了一个高精度的微妙级性能计数器(参见: http://msdn.microsoft.com/msdnmag/issues/04/03/HighResolutionTimer/default.aspx ,或者 http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=200249 
    )1)计数器的Delphi源代码{  
    A  high-precision  counter/timer.  Retrieves  time  differences  
                                   downto  microsec.  
    Quick  Reference:  
                                   THPCounter  inherits  from  TComponent.  
     
                                   Key-Methods:  
                                       Start:        Starts  the  counter.  Place  this  call  just  before  the  
                                                           code  you  want  to  measure.  
     
                                       Read:          Reads  the  counter  as  a  string.  Place  this  call  just  
                                                           after  the  code  you  want  to  measure.  
     
                                       ReadInt:    Reads  the  counter  as  an  Int64.  Place  this  call  just  
                                                           after  the  code  you  want  to  measure.  
    --------------------------------------------------------------------------------  
    }  
    unit  HPCounter;  
     
    interface  
     
    uses  
       SysUtils,  WinTypes,  WinProcs,  Messages,  Classes,  Graphics,  Controls,  
       Forms,  Dialogs,  StdCtrls,  ExtCtrls;  
     
    type  
       TInt64  =  TLargeInteger;  
       THPCounter  =  class(TComponent)  
    private  
       Frequency:  TLargeInteger;  
       lpPerformanceCount1:  TLargeInteger;  
       lpPerformanceCount2:  TLargeInteger;  
       fAbout:  string;  
       procedure  SetAbout(Value:  string);  
       {  Private  declarations  }  
    public  
       constructor  Create(AOwner:  TComponent);  override;  
       destructor  Destroy;  override;  
       procedure  Start;  
       function  Read:  string;  
       function  ReadInt:  TLargeInteger;  
       {  Private  declarations  }  
    published  
       property  About:  string  read  fAbout  write  SetAbout;  
       {  Published  declarations  }  
    end;  
     
     
    procedure  Register;  
     
    implementation  
     
    procedure  Register;  
    begin  
       RegisterComponents('MAs  Prod.',  [THPCounter]);  
    end;  
     
    constructor  THPCounter.Create(AOwner:  TComponent);  
    begin  
       inherited  Create(AOwner);  
       fAbout:=  'Version  1.1,  2000&reg;  Mats  Asplund,  EMail:  [email protected],  Site:  http://go.to/masdp';  
    end;  
     
    destructor  THPCounter.Destroy;  
    begin  
       inherited  Destroy;  
    end;  
     
    function  THPCounter.Read:  string;  
    begin  
       QueryPerformanceCounter(TInt64((@lpPerformanceCount2)^));  
       QueryPerformanceFrequency(TInt64((@Frequency)^));  
       Result:=IntToStr(Round(1000000  *  (lpPerformanceCount2  -  
                                                 lpPerformanceCount1)  /  Frequency));  
    end;  
     
    function  THPCounter.ReadInt:  TLargeInteger;  
    begin  
       QueryPerformanceCounter(TInt64((@lpPerformanceCount2)^));  
       QueryPerformanceFrequency(TInt64((@Frequency)^));  
       Result:=Round(1000000  *  (lpPerformanceCount2  -  
                                                 lpPerformanceCount1)  /  Frequency);  
    end;  
     
    procedure  THPCounter.SetAbout(Value:  string);  
    begin  
       Exit;  
    end;  
     
    procedure  THPCounter.Start;  
    begin  
       QueryPerformanceCounter(TInt64((@lpPerformanceCount1)^));  
    end;  
     
    end.  2)使用方法:  
    unit  Unit1;  
     
    interface  
     
    uses  
       Windows,  Messages,  SysUtils,  Classes,  Graphics,  Controls,  Forms,  Dialogs,  
       HPCounter,  StdCtrls;  
     
    type  
       TForm1  =  class(TForm)  
           Button1:  TButton;  
           Edit1:  TEdit;  
           Label1:  TLabel;  
           Label2:  TLabel;  
           procedure  Button1Click(Sender:  TObject);  
       private  
           {  Private  declarations  }  
       public  
           {  Public  declarations  }  
       end;  
     
    var  
       Form1:  TForm1;  
     
    implementation  
     
    {$R  *.DFM}  
     
    procedure  TForm1.Button1Click(Sender:  TObject);  
    begin  
       Edit1.Text:=  '';  
       Application.ProcessMessages;  
       with  THPCounter.Create(Self)  do  
           begin  
               Start;  
               //  Place  code  to  measure  here  
               Sleep(1000);  
               //  Place  code  to  measure  here  
               Edit1.Text:=Read;  
               Free;  
           end;  
    end;  
     
    end. 二、三种方法的精度比较  为了比较,采用以上3种方法,分别设置延时时间为1ms、2ms、5ms、10ms、20ms、50ms、100ms、200ms、500ms、1000ms,循环次数为5次,得到实际的延时时间。1)TTtimer控件                       实际延时时间(ms)
    1ms: 8.012   21.551  6.875   21.647  9.809 
    2ms: 9.957   20.675  14.671  11.903  20.551 
    5ms: 9.952   20.605  9.924   20.705  12.682 
    10ms:14.852  9.96    21.547  9.82    20.634 
    20ms:27.512  34.291  26.427  31.244  30.398 
    50ms:61.196  61.307  64.027  62.048  63.059 
    100ms:102.495 108.408 112.318 110.322 102.531 
    200ms:193.955 202.135 207.016 205.082 202.194 
    500ms:496.659 500.534 503.398 495.551 500.394 
    1000ms:999.699 1003.576 993.698 1004.443 995.625 2)Sleep函数1ms: 1.895   1.895   1.896   1.897   1.898 
    2ms: 2.868   2.874   2.852   2.872   2.869 
    5ms: 5.8     5.797   5.79    5.79    5.791 
    10ms:10.675  10.683  10.611  10.669  10.67 
    20ms:20.404  20.434  20.447  20.477  20.368 
    50ms:50.67   50.691  50.69   50.682  50.671
    100ms:100.515 100.469 100.484 100.481 100.484 
    200ms:200.101 200.126 199.892 200.066 200.108 
    500ms:499.961 499.961 499.958 499.961 499.96 
    1000ms:1000.034 1000.04 1000.03 1000.018 1000.0293)GetTickCount函数1ms: 15.54   15.596  15.527  15.566  15.838 
    2ms: 15.561  15.563  15.603  15.477  15.571 
    5ms: 15.519  15.549  15.569  15.666  15.394 
    10ms:15.558  15.561  15.522  15.568  15.518 
    20ms:31.186  31.137  31.17   31.17   31.19 
    50ms:62.445  62.4    63.893  60.88   62.404 
    100ms:109.276 109.298 109.273 109.28  109.28 
    200ms:203.027 203.084 203.021 203.027 203.046 
    500ms:499.959 499.961 499.963 499.967 499.965 
    1000ms:1000.023 1000.022 1000.026 1000.029 1000.021 
      可见,相对而言,Sleep的精度最高,尤其是在10ms以内的延时,只有sleep函数才能够做到。TTimer控件的定时精度最差,而且稳定性不好,波动很大。GetTickCount函数所能实现的最短延时为15ms左右,稳定性相对TTimer要好一些。