在項目中,需要每隔20ms發送一個RTP數據包。一開始使用的是System.Windows.Forms下的Timer類,但是發現明顯延遲了。用StopWatch測了一下,發現它的觸發間隔居然不是20ms,而是在31ms左右搖擺。換了System.Threading下的Timer和System.Timers下和Timer也不行,一樣的問題。
為什么會這樣呢?在網上發現了一段非常具有啟發性的話,它解釋了原因並給出了解決的辦法:
目前,Windows軟件一般使用Timer定時器進行定時。Timer定時器是由應用程序響應定時消息WM_TIMER實現定時。Timer定時器是IBM PC硬件和ROM BIOS構造的定時器的簡單擴充。PC的ROM初始化8253定時器來產生硬件中斷08H,而08H中斷的頻率為18.2Hz,即至少每隔54.925 ms中斷一次。此外,這個定時消息的優先權太低,只有在除WM_PAINT外的所有消息被處理完后,才能得到處理。
多媒體定時器也是利用系統定時器工作的,但它的工作機理和普通定時器有所不同。首先,多媒體定時器可以按精度要求設置8253的T/C0通道的計數初值,使定時器不存在54.945ms的限制;其次,多媒體定時器不依賴於消息機制,而是用函數產生一個獨立的線程,在一定的中斷次數到達后,直接調用預先設置好的回調函數進行處理,不必等到應用程序的消息隊列為空,從而切實保障了定時中斷得到實時響應,使其定時精度可達1ms。
這段話中的多媒體定時器被放在了winmm.dll中。要使用這個定時器,可以通過調用timeSetEvent和timeKillEvent函數來實現。為了便於使用,參照網上的一些資料,對這兩個方法進行了封裝。現跟大家分享一下。

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; } }
調用方法:
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(); }
參考: