Loading

c# 高精度计时器

使用 timeSetEvent

MMRESULT timeSetEvent( UINT uDelay, 
                        UINT uResolution, 
                        LPTIMECALLBACK lpTimeProc, 
                        WORD dwUser, 
                        UINT fuEvent )
                                 
   uDelay:以毫秒指定事件的周期。
   Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
   LpTimeProc:指向一个回调函数。
   DwUser:存放用户提供的回调数据。
   FuEvent:指定定时器事件类型:
   TIME_ONESHOT:uDelay毫秒后只产生一次事件
   TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。

c#包装类:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
/// <summary>
/// 
/// </summary>
public class MultimediaTimerWapper : IDisposable
{
    private bool disposed = false;
    private int interval;
    private int resolution;
    /// <summary>
    /// 当前定时器实例ID
    /// </summary>
    private uint timerId;

    // 保持定时器回调以防止垃圾收集。
    private readonly MultimediaTimerCallback Callback;

    /// <summary>
    /// API使用的回调
    /// </summary>
    public event EventHandler Timer;

    public MultimediaTimerWapper()
    {
        Callback = new MultimediaTimerCallback(TimerCallbackMethod);
        Resolution = 0;
    }

    ~MultimediaTimerWapper()
    {
        Dispose(false);
    }

    /// <summary>
    /// 注意最小分辨率为 0,表示可能的最高分辨率。
    /// </summary>
    public int Resolution
    {
        get
        {
            return resolution;
        }
        set
        {
            CheckDisposed();

            if (value < 0)
                throw new ArgumentOutOfRangeException("value");

            resolution = value;
        }
    }

    /// <summary>
    /// 是否在运行
    /// </summary>
    public bool IsRunning
    {
        get { return timerId != 0; }
    }

    /// <summary>
    /// 启动一个定时器实例
    /// </summary>
    /// <param name="ms">以毫秒为单位的计时器间隔</param>
    /// <param name="repeat">如果为 true 设置重复事件,否则设置一次性</param>
    public void Start(uint ms,bool repeat)
    {

        //杀死任何现有的计时器
        CheckDisposed();

        if (IsRunning)
            throw new InvalidOperationException("Timer is already running");

        //Set the timer type flags
        fuEvent f = fuEvent.TIME_CALLBACK_FUNCTION | (repeat ? fuEvent.TIME_PERIODIC : fuEvent.TIME_ONESHOT);
        // Event type = 0, one off event
        // Event type = 1, periodic event
        uint userCtx = 0;
        lock (this)
        {
            timerId = NativeMethods.TimeSetEvent(ms, (uint)Resolution, Callback, ref userCtx, (uint)f);
            if (timerId == 0)
            {
                int error = Marshal.GetLastWin32Error();
                throw new Win32Exception(error);
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public void Stop()
    {
        CheckDisposed();

        if (!IsRunning)
            throw new InvalidOperationException("Timer has not been started");

        StopInternal();
    }

    private void StopInternal()
    {
        lock (this)
        {
            if(timerId != 0)
            {
                NativeMethods.TimeKillEvent(timerId);
                timerId = 0;
            }
        }
    }



    public void Dispose()
    {
        Dispose(true);
    }

    private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2)
    {
        Timer?.Invoke(this, EventArgs.Empty);
    }

    private void CheckDisposed()
    {
        if (disposed)
            throw new ObjectDisposedException("MultimediaTimer");
    }

    private void Dispose(bool disposing)
    {
        if (disposed)
            return;

        disposed = true;
        if (IsRunning)
        {
            StopInternal();
        }

        if (disposing)
        {
            Timer = null;
            GC.SuppressFinalize(this);
        }
    }
}

internal delegate void MultimediaTimerCallback(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2);

/// <summary>
/// 此结构包含有关计时器分辨率的信息。单位是ms
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct TIMECAPS
{
    /// <summary>
    /// 支持的最小期限。
    /// </summary>
    public uint wPeriodMin;
    /// <summary>
    /// 支持的最大期限。
    /// </summary>
    public uint wPeriodMax;
}

/// <summary>
/// 定时器类型定义
/// </summary>
[Flags]
public enum fuEvent : uint
{
    TIME_ONESHOT = 0, //Event occurs once, after uDelay milliseconds.
    TIME_PERIODIC = 1,
    TIME_CALLBACK_FUNCTION = 0x0000, /* callback is function */
        //TIME_CALLBACK_EVENT_SET = 0x0010, /* callback is event - use SetEvent */
        //TIME_CALLBACK_EVENT_PULSE = 0x0020  /* callback is event - use PulseEvent */
}


internal static class NativeMethods
{

    /// <summary>
    /// 此函数启动指定的计时器事件。
    /// </summary>
    /// <param name="msDelay">事件延迟,以毫秒为单位。如果该值不在计时器支持的最小和最大事件延迟范围内,则该函数返回错误。</param>
    /// <param name="msResolution">计时器事件的分辨率,以毫秒为单位。分辨率越高,分辨率越高;零分辨率表示周期性事件应该以最大可能的精度发生。但是,为减少系统开销,应使用适合您的应用程序的最大值。</param>
    /// <param name="callback">如果fuEvent指定TIME_CALLBACK_EVENT_SET或TIME_CALLBACK_EVENT_PULSE标志,则fptc参数将解释为事件对象的句柄。事件将在单个事件完成时设置或发出脉冲,或者在周期性事件完成时定期设置或触发。对于fuEvent的任何其他值,fptc参数将被解释为函数指针。</param>
    /// <param name="userCtx">用户提供的回调数据。</param>
    /// <param name="fuEvent">计时器事件类型。下表显示了fuEvent参数可以包含的值。</param>
    [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")]
    internal static extern uint TimeSetEvent(uint msDelay, uint msResolution, MultimediaTimerCallback callback, ref uint userCtx, uint fuEvent);

    /// <summary>
    /// 此功能取消指定的计时器事件。
    /// </summary>
    /// <param name="uTimerId">要取消的计时器事件的标识符。此标识符由timeSetEvent函数返回,该函数启动指定的计时器事件。</param>
    /// <returns></returns>
    [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")]
    internal static extern void TimeKillEvent(uint uTimerId);

    /// <summary>
    /// 此函数查询计时器设备以确定其分辨率。
    /// </summary>
    /// <param name="ptc">指向TIMECAPS结构的指针。该结构充满了有关计时器设备分辨率的信息。</param>
    /// <param name="cbtc">TIMECAPS结构的大小(以字节为单位)。</param>
    /// <returns>如果成功,则返回TIMERR_NOERROR,如果未能返回计时器设备功能,则返回TIMERR_STRUCT。</returns>
    [DllImport("winmm.dll")]
    internal static extern uint timeGetDevCaps(ref TIMECAPS ptc, int cbtc);


    [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
    internal static extern uint timeGetTime();

    [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
    internal static extern uint timeBeginPeriod(uint uPeriod);

    [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
    internal static extern uint timeEndPeriod(uint uPeriod);
}

调用方式:

static void Main(string[] args)
{
    MultimediaTimerWapper multimediaTimer = new MultimediaTimerWapper();
    multimediaTimer.Resolution = 1;
    multimediaTimer.Timer += MultimediaTimer_Timer;
    multimediaTimer.Start(1000,false);
    Console.ReadKey();
    multimediaTimer.Stop();
}
private static void MultimediaTimer_Timer(object sender, EventArgs e)
{
    Console.WriteLine("执行完成");
}

使用 CreateTimerQueueTimer 定时器

定时器队列(Timer Queue)可以使用CreateTimerQueue函数创建。定时器队列中的定时器是轻量级对象,可以在一定时间间隔之后调用指定的回调函数(可以只调用一次,也可以是周期性的)。这种等待操作由线程池中某个线程处理的(系统级别)。

向定时器队列中添加定时器可以使用CreateTimerQueueTimer函数。更新一个计时器队列中的计时器可以使用 ChangeTimerQueueTimer 函数。这两个函数中你可以指定一个回调函数,如果设定时间到达,线程池中某个线程会调用该回调函数。

使用 DeleteTimerQueueTimer函数可以取消挂起的计时器。当不再使计时器队列的时候,调用 DeleteTimerQueueEx 函数删除计时器队列,该函数调用是会取消并删除任何在该队列中等待的计时器。

1.HANDLE CreateTimerQueue():该函数创建一个定时器队列,定时器队列对一组定时器进行组织安排。一旦拥有一个定时器队列,就可以在该队列中创建下面的定时器:

2.BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, HANDLE hTimerQueue,WAITORTIMERCALLBACK pfnCallback,PVOID pvContext,DWORD dwDueTimer,DWORD dwPeriod,ULONG dwFlags):如果只是创建少数几个定时器,只需要为hTimerQueue参数传递NULL,并且完全避免调用CreateTimerQueue函数。这会使用默认的定时器队列,并且简化你的代码。工作回调函数必须采用下面的原型: VOID WINAPI WaitOrTimerCallback(PVOID pvContext,BOOL fTimerOrWatiFired);

dwFlags参数的取值:WT_EXECUTEDEFAULT(用非I/O组件处理工作项目),WT_EXECUTEINIOTHREAD(用I/O组件),WT_EXECUTEPERSISTENTTHREAD(用定时器),WT_EXECUTELONGFUNCTION(工作项目需要花费较长时间时)
还有一个标志是WT_EXECUTEINTIMERTHREAD,它使定时器组件的线程可以执行工作项目,虽然这可以使工作项目的运行效率更高,但是这非常危险,如果工作项目长时间中断运行,那么等待定时器的线程就无法执行任何其他操作。如果打算这么做,那么该代码应该迅速执行,不应该中断。
回调函数的原型:VOID WINAPI WaitOrTimerCallbackFunc( PVOID pvContext, BOOLEAN fTimerOrWaitFired);

3.可以调用 BOOL ChangeTimerQueueTimer(HANDLE hTimerQueue ,HANDLE hTimer, ULONG dwDueTimer,ULONG dwPeriod)函数来改变它的到期时间和到期周期。
4.当不再使用定时器时,必须通过调用下面的函数将它删除: BOOL DeleteTimerQueueTimer(HANDLE hTimerQueue,HANDLE hTimer, HANDLE hCompletionEvent)
5. 当要删除定时器队列:BOOL DeleteTimerQueueEX(HANDLE hTimerQueue,HANDLE hCompletionEvent);

c#包装类:

using System;
using System.Runtime.InteropServices;
/// <summary>
/// 高精度计时器
/// </summary>
public class TimerQueueTimerWapper : IDisposable
{

    /// <summary>
    /// Handle to the timer.(处理定时器。)
    /// </summary>
    private IntPtr phNewTimer;

    #region Win32 TimerQueueTimer 函数

        /// <summary>
        /// 
        /// </summary>
        /// <param name="phNewTimer">Pointer to a handle; this is an out value(指向句柄的指针; 这是一个out值)</param>
        /// <param name="TimerQueue">Timer queue handle. For the default timer queue, NULL (定时器队列句柄。 对于默认计时器队列,NULL)</param>
        /// <param name="Callback">Pointer to the callback function (指向回调函数的指针)</param>
        /// <param name="Parameter">Value passed to the callback function (传递给回调函数的值)</param>
        /// <param name="DueTime">Time (milliseconds), before the timer is set to the signaled state for the first time
        /// (时间(毫秒),在定时器第一次设置为信号状态之前)</param>
        /// <param name="Period">Time period (milliseconds). If zero, timer is signaled only once(计时器周期(毫秒)。如果为零,则计时器仅发出一次信号)</param>
        /// <param name="Flags">One or more of the next values (table taken from MSDN):
        ///     WT_EXECUTEINTIMERTHREAD 	The callback function is invoked by the timer thread itself. This flag should be used only for short tasks or it could affect other timer operations.
        ///     WT_EXECUTEINIOTHREAD 	The callback function is queued to an I/O worker thread. This flag should be used if the function should be executed in a thread that waits in an alertable state.
        /// </param>
        /// <remarks>
        /// The callback function is queued as an APC. Be sure to address reentrancy issues if the function performs an alertable wait operation.
        /// WT_EXECUTEINPERSISTENTTHREAD 	The callback function is queued to a thread that never terminates. This flag should be used only for short tasks or it could affect other timer operations.

        /// Note that currently no worker thread is persistent, although no worker thread will terminate if there are any pending I/O requests.
        /// WT_EXECUTELONGFUNCTION 	Specifies that the callback function can perform a long wait. This flag helps the system to decide if it should create a new thread.
        /// WT_EXECUTEONLYONCE 	The timer will be set to the signaled state only once.
        /// </remarks>
        /// <returns></returns>
        [DllImport("kernel32.dll")]
        private static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer, IntPtr TimerQueue,WaitOrTimerDelegate Callback,
                                                         IntPtr Parameter,uint DueTime,uint Period, uint Flags);

    /// <summary>
    /// 
    /// </summary>
    /// <param name="timerQueue">A handle to the (default) timer queue</param>
    /// <param name="timer">A handle to the timer</param>
    /// <param name="completionEvent">A handle to an optional event to be signaled when the function is successful and all callback functions have completed. Can be NULL.</param>
    /// <returns></returns>
    [DllImport("kernel32.dll")]
    private static extern bool DeleteTimerQueueTimer(IntPtr timerQueue,IntPtr timer, IntPtr completionEvent);


    [DllImport("kernel32.dll")]
    private static extern bool DeleteTimerQueue(IntPtr TimerQueue);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    #endregion

        public delegate void WaitOrTimerDelegate(IntPtr lpParameter, bool timerOrWaitFired);

    public TimerQueueTimerWapper()
    {

    }

    /// <summary>
    /// 创建定时器
    /// </summary>
    /// <param name="dueTime">定时器第一次设置为信号状态之前的时间(毫秒)。</param>
    /// <param name="period">Period - 计时器周期(毫秒)。 如果为零,则计时器仅发出一次信号。</param>
    /// <param name="callbackDelegate">回调函数参数</param>
    public void Create(uint dueTime, uint period, WaitOrTimerDelegate callbackDelegate)
    {
        IntPtr pParameter = IntPtr.Zero;

        bool success = CreateTimerQueueTimer(
            // Timer handle
            out phNewTimer,
            // Default timer queue. IntPtr.Zero is just a constant value that represents a null pointer.
            IntPtr.Zero,
            // Timer callback function
            callbackDelegate,
            // Callback function parameter
            pParameter,
            // Time (milliseconds), before the timer is set to the signaled state for the first time.
            // 定时器第一次设置为信号状态之前的时间(毫秒)。
            dueTime,
            // Period - Timer period (milliseconds). If zero, timer is signaled only once.
            //Period - 计时器周期(毫秒)。 如果为零,则计时器仅发出一次信号。
            period,
            (uint)Flag.WT_EXECUTEINIOTHREAD);

        if (!success)
            throw new QueueTimerException("Error creating QueueTimer");
    }

    /// <summary>
    /// 删除定时器
    /// </summary>
    public void Delete()
    {
        //bool success = DeleteTimerQueue(IntPtr.Zero);
        bool success = DeleteTimerQueueTimer(
            IntPtr.Zero, // TimerQueue - A handle to the (default) timer queue
            phNewTimer,  // Timer - A handle to the timer
            IntPtr.Zero  // CompletionEvent - A handle to an optional event to be signaled when the function is successful and all callback functions have completed. Can be NULL.
        );
        int error = Marshal.GetLastWin32Error();
        //CloseHandle(phNewTimer);
    }

    private enum Flag
    {
        WT_EXECUTEDEFAULT = 0x00000000,
        WT_EXECUTEINIOTHREAD = 0x00000001,
        //WT_EXECUTEINWAITTHREAD       = 0x00000004,
        WT_EXECUTEONLYONCE = 0x00000008,
        WT_EXECUTELONGFUNCTION = 0x00000010,
        WT_EXECUTEINTIMERTHREAD = 0x00000020,
        WT_EXECUTEINPERSISTENTTHREAD = 0x00000080,
        //WT_TRANSFER_IMPERSONATION    = 0x00000100
    }

    #region IDisposable Members

        void IDisposable.Dispose()
    {
        Delete();
    }

    #endregion
}

public class QueueTimerException : Exception
{
    public QueueTimerException(string message) : base(message)
    {
    }

    public QueueTimerException(string message, Exception innerException)
        : base(message, innerException)
        {
        }
}

调用方式:

static void Main(string[] args)
{
    TimerQueueTimerWapper qt = new TimerQueueTimerWapper();
    TimerQueueTimerWapper.WaitOrTimerDelegate callbackDelete = new TimerQueueTimerWapper.WaitOrTimerDelegate(QueueTimerCallback);
    qt.Create(2000, 0, callbackDelete);
    Console.ReadKey();
    qt.Delete();
}
private static void QueueTimerCallback(IntPtr lpParameter, bool timerOrWaitFired)
{
    Console.WriteLine("执行完成");
}
posted @ 2021-11-05 15:26  F(x)_King  阅读(1523)  评论(0编辑  收藏  举报