3

Windows 7 ではタイマー結合が導入され、エネルギー効率が向上しました。タイマーの許容範囲を公開するマネージ API はどれですか? この機能を利用する唯一の方法は、SetWaitableTimerExを p/invoke することです。

4

1 に答える 1

5

私が認識しているマネージ API はありませんが、そうは言っても、それはそれほど面倒ではない P/Invokes の 1 つです。

(私はこれを非常に基本的にテストしただけであることに注意してください...おそらく微調整が必​​要です)[編集:わかりました、昼食時に少し調整する機会がありました。これは多かれ少なかれうまくいくはずです]

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  
}
于 2012-12-06T17:07:27.993 に答える