1. 程式人生 > >C#實現高精度定時器

C#實現高精度定時器

 

轉自https://blog.csdn.net/nocky/article/details/6056413

這兩天正在準備做一個實時控制的東西,想用C#。可是昨天日本人展示了一個在LINUX平臺下使用C語言控制的單自由度機械臂,我問他們為什麼不用WINDOWS,他們說用WINDOWS程式設計實時性很差,定時很不準,所以用了LINUX,為了相容性好,伺服器也用的是LINUX平臺,用於網路控制。可是如果網路也用C或C++的話程式設計肯定比較慢,還是想用C#程式設計,於是今天就研究了一下C#中定時器的問題。

在.Net Framework中在三個地方有定時器類分別是System.Timers.Timer, System.Threading.Timer和System.Windows.Form.Timer。與了個程式分別對這三個類進行了測試,精度的確差得不能用,三個定時精度都差不多,最好可以到15ms左右,但很不穩定,定時間隔誤差比較大,五般在15個ms左右,甚至更多,經過MATLAB畫出的曲線看,誤差還是有規律的,下面是定時間隔為40ms時,定時器中斷服務函式相臨兩次響應之間的時間間隔取樣資料連線圖。

由圖中可以看出,在很多時候實際響應時間要比設定時間晚10毫秒左右,但取樣時間都已經40ms了,如果把取樣時間再調小一些誤差會更大,而且經過測試,最小間隔只能到15ms,即使把間隔調成1ms,實際響應時間都在15ms以上,這種精度在實時控制中幾乎不能接受,故而需要使用其它方法提高定時器的精度。

因為WINDOWS是多工的作業系統,定時器的延時可能是由於其它任務佔用CPU資源,沒能及時響應定時器事件,如果能夠改變定時器執行緒的優先順序應該也可以改善定時器的精度,但查了一些資料也沒有看到能夠控制那些定時器類封裝的純種的優先順序的資料,因為這些類都沒有提供控制定時器所線上程的優先順序的介面。故而想自己構造一個高精度的定時器,使得精度儘量能達到1ms。

在查閱資料中發現兩篇文章很有用,一個是介紹VC使用WIN32API函式


 

///


/// Pointer to a variable that receives the current performance-counter value, in counts. 
/// 
/// 
/// If the function succeeds, the return value is nonzero. 
/// 
[DllImport("Kernel32.dll")] 
private static extern bool QueryPerformanceFrequency(out long lpPerformanceCount);



///


/// Pointer to a variable that receives the current performance-counter frequency, 
/// in counts per second. 
/// If the installed hardware does not support a high-resolution performance counter, 
/// this parameter can be zero. 
/// 
/// 
/// If the installed hardware supports a high-resolution performance counter, 
/// the return value is nonzero. 
/// 
[DllImport("Kernel32.dll")] 
private static extern bool QueryPerformanceFrequency(out  long lpFrequency);

另一篇就是介紹在C#中使用這兩個函式的方法了,也就是上面的程式碼。QueryPerformanceFrequency 函式是讀取系統所支援的硬體定時器計數頻率,也就是每秒的計數值,QueryPerformanceFrequency 應該是從開機時的計數值。這樣通過讀取兩次計數值之差,再除以計數頻率也就是兩次讀取時鐘間隔的秒數。然後通過查詢兩次的間隔來確定是否響應服務函式,封裝瞭如下類:


 

  /// 
    /// ManualTimer
    /// A simulated timer by loop 
    /// It creates a new thread in Thread Pool using ThreadPool class
    /// Nocky Tian @ 2008-3-16
    /// 
    /// The timer starts a new thread using  object,
    /// and the value of the property Priority is set to 
    /// so that the accuray could be kept 1ms around.
    /// 
    /// 
    /// 
    /// 
    public class UltraHighAccurateTimer
    {
        public event ManualTimerEventHandler tick;
        private object threadLock = new object();       // for thread safe
        private long clockFrequency;            // result of QueryPerformanceFrequency() 
        bool running = true;
        Thread thread ;

        private int intervalMs;                     // interval in mimliseccond;

        /// 
        /// Timer inteval in milisecond
        /// 
        public int Interval
        {
            get { return intervalMs; }
            set
            {
                intervalMs = value;
                intevalTicks = (long)((double)value * (double)clockFrequency / (double)1000);
            }
        }
        private long intevalTicks;
        private long nextTriggerTime;               // the time when next task will be executed

        /// 
        /// Pointer to a variable that receives the current performance-counter value, in counts. 
        /// 
        /// 
        /// If the function succeeds, the return value is nonzero.
        /// 
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        /// 
        /// Pointer to a variable that receives the current performance-counter frequency, 
        /// in counts per second. 
        /// If the installed hardware does not support a high-resolution performance counter, 
        /// this parameter can be zero. 
        /// 
        /// 
        /// If the installed hardware supports a high-resolution performance counter, 
        /// the return value is nonzero.
        /// 
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out  long lpFrequency);


        protected void OnTick()
        {
            if (tick != null) {
                tick();
            }
        }

        public UltraHighAccurateTimer()
        {
            if (QueryPerformanceFrequency(out clockFrequency) == false) {
                // Frequency not supported
                throw new Win32Exception("QueryPerformanceFrequency() function is not supported");
            }

            thread = new Thread(new ThreadStart(ThreadProc));
            thread.Name = "HighAccuracyTimer";
            thread.Priority = ThreadPriority.Highest;
        }

        /// 
        /// 程序主程式
        /// 
        /// 
        private void ThreadProc()
        {
            long currTime;
            GetTick(out currTime);
            nextTriggerTime = currTime + intevalTicks;
            while (running) {
                while (currTime < nextTriggerTime) {
                    GetTick(out currTime);
                }   // wailt an interval
                nextTriggerTime = currTime + intevalTicks;
                //Console.WriteLine(DateTime.Now.ToString("ss.ffff"));
                if (tick != null) {
                    tick();
                }
            }
        }

        public bool GetTick(out long currentTickCount)
        {
            if (QueryPerformanceCounter(out currentTickCount) == false)
                throw new Win32Exception("QueryPerformanceCounter() failed!");
            else
                return true;
        }

        public void Start()
        {
            thread.Start();
        }
        public void Stop()
        {
            running = false;
        }

        ~UltraHighAccurateTimer()
        {
            running = false;
            thread.Abort();
        }
    }


  本來上面的類是用執行緒池建立的定時器執行緒,使用的ThreadPool類,但是精度不高,只能到5ms,或4ms,不是很滿意,改用Thread類,將其優秀級提到最高,精度可以達到1ms. 可能是作業系統調整優先順序順序問題,取樣200次,發現到四十幾次以後精度才能到1ms,一開始取樣間隔有2~3ms,嘗試一開始先執行一段空程式,使執行緒進入正常以後才開始定時器的工作,可基本沒有什麼改善,也有可能等待的時間不夠。

上面的程式在VISTA上測試過,可以達到1ms的精度,但需要一個過渡期。可能是由於任務太多造成的,因為在測試程式中把時間顯示到終端螢幕了,而且裡面有一些字串格式化操作,會比較佔用資源,而且使用了delegate作為事件代理,可能效能有所損失,以後再研究一下delegate對效能損失的程度。

如果系統對實時性要求比較高,可以不對定時器進行封裝,將定時器的主程序當作控制器取樣的主程序,減少函式呼叫造成的CPU資源浪費,精度會更高。
--------------------- 
作者:noock 
來源:CSDN 
原文:https://blog.csdn.net/nocky/article/details/6056413 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

 

謝謝了哦,這篇文章為博主2008年編寫,使用的開發工具可能為vs2005,我用vs2010編譯不了,後用了

https://blog.csdn.net/clb929/article/details/54024880中的修改後的程式碼進行編譯,可以編譯成功,下面貼出程式碼

UltraHighAccurateTimer.cs

using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace UltraHighAccurateTimer
{
    ///
    /// ManualTimer
    /// A simulated timer by loop
    /// It creates a new thread in Thread Pool using ThreadPool class
    /// Nocky Tian @ 2008-3-16
    ///
    /// The timer starts a new thread using  object,
    /// and the value of the property Priority is set to
    /// so that the accuray could be kept 1ms around.
    ///
    ///
    ///
    ///
    public class UltraHighAccurateTimer
    {
        public delegate void ManualTimerEventHandler(object sender);

        public event ManualTimerEventHandler Tick;

        private long clockFrequency;            // result of QueryPerformanceFrequency()
        private bool running = false;
        private Thread timerThread;

        private int intervalMs;                     // interval in mimliseccond;

        ///
        /// Timer inteval in milisecond
        ///
        public int Interval
        {
            get { return intervalMs; }
            set
            {
                intervalMs = value;
                intevalTicks = (long)((double)value * (double)clockFrequency / (double)1000);
            }
        }

        private long intevalTicks;

        ///
        /// Pointer to a variable that receives the current performance-counter value, in counts.
        ///
        ///
        /// If the function succeeds, the return value is nonzero.
        ///
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        ///
        /// Pointer to a variable that receives the current performance-counter frequency,
        /// in counts per second.
        /// If the installed hardware does not support a high-resolution performance counter,
        /// this parameter can be zero.
        ///
        ///
        /// If the installed hardware supports a high-resolution performance counter,
        /// the return value is nonzero.
        ///
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        public UltraHighAccurateTimer()
        {
            if (QueryPerformanceFrequency(out clockFrequency) == false)
            {
                // Frequency not supported
                throw new Win32Exception("QueryPerformanceFrequency() function is not supported");
            }
        }

        ///
        /// 程序主程式
        ///
        ///
        private void ThreadProc()
        {
            long currTime;
            long nextTriggerTime;               // the time when next task will be executed
            GetTick(out currTime);
            nextTriggerTime = currTime + intevalTicks;
            while (running)
            {
                while (currTime < nextTriggerTime)
                {
                    GetTick(out currTime);
                }   // wailt an interval
                nextTriggerTime = currTime + intevalTicks;
                if (Tick != null)
                {
                    Tick(this);
                }
            }
        }

        public bool GetTick(out long currentTickCount)
        {
            if (QueryPerformanceCounter(out currentTickCount) == false)
                throw new Win32Exception("QueryPerformanceCounter() failed!");
            else
                return true;
        }

        public void Start()
        {
            running = true;

            timerThread = new Thread(new ThreadStart(ThreadProc));
            timerThread.Name = "HighAccuracyTimer";
            timerThread.Priority = ThreadPriority.Highest;

            timerThread.Start();
        }

        public void Stop()
        {
            running = false;
            timerThread.Abort();
        }

        ~UltraHighAccurateTimer()
        {
            running = false;
            timerThread.Abort();
        }
    }
}

Program.cs

using System;

namespace UltraHighAccurateTimerPractice
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            UltraHighAccurateTimer.UltraHighAccurateTimer uhat = new UltraHighAccurateTimer.UltraHighAccurateTimer();
            uhat.Interval = 100;  //定時間隔100毫秒
            uhat.Tick += new UltraHighAccurateTimer.UltraHighAccurateTimer.ManualTimerEventHandler(uhat_Tick);
            uhat.Start();
        }

        private static void uhat_Tick(object sender)
        {
            Console.WriteLine(DateTime.Now.ToString("ss.ffff"));
        }
    }
}