Timer計時不准確的問題及解決方法


     在項目中,需要每隔20ms發送一個RTP數據包。一開始使用的是System.Windows.Forms下的Timer類,但是發現明顯延遲了。用StopWatch測了一下,發現它的觸發間隔居然不是20ms,而是在31ms左右搖擺。換了System.Threading下的TimerSystem.Timers下和Timer也不行,一樣的問題。

 

     為什么會這樣呢?在網上發現了一段非常具有啟發性的話,它解釋了原因並給出了解決的辦法:

 

    目前,Windows軟件一般使用Timer定時器進行定時。Timer定時器是由應用程序響應定時消息WM_TIMER實現定時。Timer定時器是IBM PC硬件和ROM BIOS構造的定時器的簡單擴充。PCROM初始化8253定時器來產生硬件中斷08H,而08H中斷的頻率為18.2Hz即至少每隔54.925 ms中斷一次。此外,這個定時消息的優先權太低,只有在除WM_PAINT外的所有消息被處理完后,才能得到處理。

    多媒體定時器也是利用系統定時器工作的,但它的工作機理和普通定時器有所不同。首先,多媒體定時器可以按精度要求設置8253T/C0通道的計數初值,使定時器不存在54.945ms的限制;其次,多媒體定時器不依賴於消息機制,而是用函數產生一個獨立的線程,在一定的中斷次數到達后,直接調用預先設置好的回調函數進行處理,不必等到應用程序的消息隊列為空,從而切實保障了定時中斷得到實時響應,使其定時精度可達1ms。

 

     這段話中的多媒體定時器被放在了winmm.dll中。要使用這個定時器,可以通過調用timeSetEventtimeKillEvent函數來實現。為了便於使用,參照網上的一些資料,對這兩個方法進行了封裝。現跟大家分享一下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;


public sealed class MillisecondTimer : IComponent, IDisposable
{
  
    //*****************************************************  字 段  *******************************************************************
    private static TimerCaps caps;
    private int interval;
    private bool isRunning;
    private int resolution;
    private TimerCallback timerCallback;
    private int timerID; 


    //*****************************************************  屬 性  *******************************************************************
    /// <summary>
    /// 
    /// </summary>
    public int Interval
    {
        get
        {
            return this.interval;
        }
        set
        {
            if( ( value < caps.periodMin ) || ( value > caps.periodMax ) )
            {
                throw new Exception( "超出計時范圍!" );
            }
            this.interval = value;
        }
    }


    /// <summary>
    /// 
    /// </summary>
    public bool IsRunning
    {
        get
        {
            return this.isRunning;
        }
    }



    /// <summary>
    /// 
    /// </summary>
    public ISite Site
    {
        set;
        get;
    }





    //*****************************************************  事 件  *******************************************************************

    public event EventHandler Disposed;  // 這個事件實現了IComponet接口
    public event EventHandler Tick;




    //***************************************************  構造函數和釋構函數  ******************************************************************

    static MillisecondTimer()
    {
        timeGetDevCaps( ref caps, Marshal.SizeOf( caps ) );
    }


    public MillisecondTimer()
    {
        this.interval = caps.periodMin;    // 
        this.resolution = caps.periodMin;  //

        this.isRunning = false;
        this.timerCallback = new TimerCallback( this.TimerEventCallback );

    }


    public MillisecondTimer( IContainer container )
        : this()
    {
        container.Add( this );
    }



    ~MillisecondTimer()
    {
        timeKillEvent( this.timerID );
    }



    //*****************************************************  方 法  *******************************************************************



    /// <summary>
    /// 
    /// </summary>
    public void Start()
    {
        if( !this.isRunning )
        {
            this.timerID = timeSetEvent( this.interval, this.resolution, this.timerCallback, 0, 1 ); // 間隔性地運行

            if( this.timerID == 0 )
            {
                throw new Exception( "無法啟動計時器" );
            }
            this.isRunning = true;
        }
    }



    /// <summary>
    /// 
    /// </summary>
    public void Stop()
    {
        if( this.isRunning )
        {
            timeKillEvent( this.timerID );
            this.isRunning = false;
        }
    }




    /// <summary>
    /// 實現IDisposable接口
    /// </summary>
    public void Dispose()
    {
        timeKillEvent( this.timerID );
        GC.SuppressFinalize( this );
        EventHandler disposed = this.Disposed;
        if( disposed != null )
        {
            disposed( this, EventArgs.Empty );
        }
    }



    //***************************************************  內部函數  ******************************************************************
    [DllImport( "winmm.dll" )]
    private static extern int timeSetEvent( int delay, int resolution, TimerCallback callback, int user, int mode );


    [DllImport( "winmm.dll" )]
    private static extern int timeKillEvent( int id );


    [DllImport( "winmm.dll" )]
    private static extern int timeGetDevCaps( ref TimerCaps caps, int sizeOfTimerCaps );
    //  The timeGetDevCaps function queries the timer device to determine its resolution. 



    private void TimerEventCallback( int id, int msg, int user, int param1, int param2 )
    {
        if( this.Tick != null )
        {
            this.Tick( this, null );  // 引發事件
        }
    }






    //***************************************************  內部類型  ******************************************************************

    private delegate void TimerCallback( int id, int msg, int user, int param1, int param2 ); // timeSetEvent所對應的回調函數的簽名


    /// <summary>
    /// 定時器的分辨率(resolution)。單位是ms,毫秒?
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    private struct TimerCaps
    {
        public int periodMin;
        public int periodMax;
    }

}
View Code

 

調用方法:

 

        MillisecondTimer timer1;


        private void button1_Click( object sender, EventArgs e )
        {
            timer1 = new MillisecondTimer();
            timer1.Interval = 1;
            timer1.Tick += new EventHandler( timer1_Tick );
            timer1.Start();
        }


        int i;
        void timer1_Tick( object sender, EventArgs e )
        {
            Console.WriteLine( i++ );
        }



        private void button2_Click( object sender, EventArgs e )
        {
            timer1.Stop();
            timer1.Dispose();
        }

 

參考:

.NET中的三種Timer的區別和用法(轉)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM