计时器在.net中合并

时间:2012-12-06 15:35:18

标签: .net timer

Windows 7引入了计时器合并,提高了能效。什么托管API暴露计时器容差?似乎利用此功能的唯一方法是p / invoke SetWaitableTimerEx

1 个答案:

答案 0 :(得分:5)

没有我知道的托管API,但是说,这是一个不那么毛茸茸的P / Invokes之一 - 这是一个快速而又脏的类,我只是简单的用法:< / p>

(我应该注意到我只是非常基本上测试了这个...可能需要一些调整) [编辑:好的,有机会在午餐时调整一下,这应该工作,或多或少]

void Main()
{
    var waitFor = 6000;
    var tickAt = 2000;
    var tickEvery = 1000;
    var sw = Stopwatch.StartNew();  
    var running = true;

    var apcTask = Task.Factory.StartNew(() => 
    { 
        try 
        {
            Console.WriteLine("APC:Creating timer...");
            ApcTimer timer = new ApcTimer(@"Global\WillThisWork", tickAt, tickEvery, true);
            timer.Tick += (o,e) => 
            {
                Console.WriteLine("APC:Hey, it worked! - delta:{0}", sw.Elapsed);
            };
            Console.WriteLine("APC:Starting timer...");
            timer.Start();

            while(running);

            Console.WriteLine("APC:Stopping timer...");
            timer.Dispose();
            Console.WriteLine("APC:Finishing - delta:{0}", sw.Elapsed);
        } 
        catch(Exception ex)
        {
            Console.WriteLine(ex);
        }
    });

    Thread.Sleep(waitFor);
    running = false;
    Task.WaitAll(apcTask);
}

public class ApcTimer : IDisposable
{
    public delegate void TimerApcCallback(object sender, EventArgs args);
    public event TimerApcCallback Tick;

    private const long _MILLISECOND = 10000;
    private const long _SECOND = 10000000;

    private IntPtr _hTimer = IntPtr.Zero;
    private long _delayInMs;
    private int _period;
    private bool _resumeFromSleep;
    private Task _alerter;
    private CancellationTokenSource _cancelSource;
    private bool _timerRunning;

    public ApcTimer(
        string timerName, 
        long delayInMs, 
        int period, 
        bool resumeFromSleep)       
    {
        _hTimer = CreateWaitableTimer(IntPtr.Zero, false,timerName);
        if(_hTimer == IntPtr.Zero)
        {
            // This'll grab the last win32 error nicely
            throw new System.ComponentModel.Win32Exception();
        }   
        _delayInMs = delayInMs;
        _period = period;
        _resumeFromSleep = resumeFromSleep;
    }

    public void Start()
    {
        var sw = Stopwatch.StartNew();
        Debug.WriteLine("ApcTimer::Starting timer...");
        StopAlerter();

        SetTimer(_delayInMs);
        _cancelSource = new CancellationTokenSource();
        _alerter = Task.Factory.StartNew(
            ()=>
            {       
                _timerRunning = true;
                while(_timerRunning)
                {
                    var res = WaitForSingleObject(_hTimer, -1);
                    if(res == WaitForResult.WAIT_OBJECT_0)
                    {
                        if(Tick != null)
                        {
                            Tick.Invoke(this, new EventArgs());
                        }
                        SetTimer(_period);
                    }
                }
            }, _cancelSource.Token);

        Debug.WriteLine("ApcTimer::Started!");
    }

    public void Dispose()
    {
        Debug.WriteLine("ApcTimer::Stopping timer...");
        StopAlerter();
        CancelPendingTimer();

        if(_hTimer != IntPtr.Zero)
        {
            var closeSucc = CloseHandle(_hTimer);
            if(!closeSucc)
            {
                throw new System.ComponentModel.Win32Exception();
            }
            _hTimer = IntPtr.Zero;
        }
        Debug.WriteLine("ApcTimer::Stopped!");
    }

    private void SetTimer(long waitMs)
    {
        // timer delay is normally in 100 ns increments
        var delayInBlocks = new LARGE_INTEGER() { QuadPart = (waitMs * _MILLISECOND * -1)};
        bool setSucc = false;
        setSucc = SetWaitableTimer(_hTimer, ref delayInBlocks, 0, IntPtr.Zero, IntPtr.Zero, _resumeFromSleep);
        if(!setSucc)
        {
            // This'll grab the last win32 error nicely
            throw new System.ComponentModel.Win32Exception();
        }
    }

    private void CancelPendingTimer()
    {
        if(_hTimer != IntPtr.Zero)
        {
            Debug.WriteLine("ApcTimer::Cancelling pending timer...");
            CancelWaitableTimer(_hTimer);
        }
    }

    private void StopAlerter()
    {
        _timerRunning = false;
        if(_alerter != null)
        {
            Debug.WriteLine("ApcTimer::Shutting down alerter...");
            _cancelSource.Cancel();
            Task.WaitAll(_alerter);
        }
    }

    #region secret pinvoke goodness
    [DllImport("Kernel32.dll", SetLastError=true)]
    static extern WaitForResult WaitForSingleObject([In] IntPtr hHandle, int dwMilliseconds);

    [DllImport("Kernel32.dll", SetLastError=true)]
    [return:MarshalAs(UnmanagedType.Bool)]
    static extern bool CancelWaitableTimer([In] IntPtr hTimer);

    [DllImport("Kernel32.dll", SetLastError=true)]
    [return:MarshalAs(UnmanagedType.Bool)]
    static extern bool SetWaitableTimer(
        [In] IntPtr hTimer, 
        [In] ref LARGE_INTEGER dueTime, 
        [In] int period, 
        [In] IntPtr completionRoutine, 
        [In] IntPtr argToCallback, 
        [In] bool resume);

    [DllImport("Kernel32.dll", SetLastError=true)]
    static extern IntPtr CreateWaitableTimer(
        IntPtr securityAttributes, 
        bool manualReset,
        string timerName);

    [DllImport("Kernel32.dll", SetLastError=true)]
    static extern IntPtr CreateWaitableTimerEx(
        IntPtr securityAttributes, 
        string timerName, 
        TimerCreateFlags flags, 
        TimerAccessFlags desiredAccess);

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

    private const int INFINITE_TIMEOUT = 1;

    [Flags]
    private enum WaitForResult : int
    {
        WAIT_ABANDONED = 0x00000080,
        WAIT_OBJECT_0 = 0,
        WAIT_TIMEOUT = 0x00000102,
        WAIT_FAILED = -1
    }
    [Flags]
    private enum TimerAccessFlags : int
    {
        TIMER_ALL_ACCESS = 0x1F0003,
        TIMER_MODIFY_STATE = 0x0002,
        TIMER_QUERY_STATE = 0x0001
    }
    [Flags]
    private enum TimerCreateFlags : int
    {
        CREATE_WAITABLE_TIMER_MANUAL_RESET = 0x00000001
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LargeIntegerSplitPart 
    {
        public uint LowPart;
        public int HighPart;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct LARGE_INTEGER 
    {
        [FieldOffset(0)]
        public LargeIntegerSplitPart u;
        [FieldOffset(0)]
        public long QuadPart;
    }
    #endregion  
}