2012-12-06 29 views
3

Windows 7引入了定時器合併,提高能源效率。託管的API公開了定時器公差?似乎利用此功能的唯一方法是調用SetWaitableTimerEx定時器聚合in .net

+0

[SetCoalescableTimer](http://msdn.microsoft.com/zh-cn/library/windows/de sktop/hh405404%28v = vs.85%29.aspx)允許設置指定合併容差延遲的'uTrance_Delay',以毫秒爲單位。 – Arno

+1

@Arno SetCoalescableTimer需要p/invoke和Windows 8. –

+0

如果需要,SetThreadpoolTimer支持Vista下的計時器合併,請參閱http://stackoverflow.com/questions/23689510/timer-coalescing-before-windows-7 –

回答

5

我沒有注意到的託管API,但這就是說,這是一個不那麼毛茸茸的P/Invokes要做的事情 - 這是一個快速而骯髒的類,我只是把它們放在一起,簡單的用法:

(我要指出,我只非常基本測試這個......可能需要一些調整) [編輯:好吧,有機會調整它有點過午餐,這應該工作,更或更少]

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 
}