using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace WCell.Utilities { #region Timer Instance /// /// Indicates the mode of a . /// public enum TimerMode { /// /// Indicates that the timer will execute only once. /// OneShot = 0x00000008, /// /// Indicates that the timer will execute multiple times. /// Periodic = 0x0, } /// /// Indicates the thread that the callback will be executed in. /// public enum TimerExecutionContext { /// /// By default, the callback function is queued to a non-I/O worker thread. /// /// /// This maps to WT_EXECUTEDEFAULT. /// Default = 0x00000000, /// /// Queued on a non-I/O worker thread (persistent). /// /// /// This maps to WT_EXECUTEINPERSISTENTTHREAD. /// WorkerThread = 0x00000080, /// /// Queued on an I/O worker thread (use only with alertable I/O events). /// IOThread = 0x00000001, /// /// Execute in the timer thread (use only with fast routines that don't block for long). /// TimerThread = 0x00000020, } /// /// Callback delegate for handling. /// /// The timer that was fired. public delegate void TimerCallback(Timer timer); /// /// An individual timer instance in a . /// public class Timer { internal IntPtr Handle; internal int ID; internal LinkedListEntry Entry; /// /// The that owns the current timer instance. /// public readonly TimerQueue Queue; /// /// The the timer is operating in. /// public readonly TimerMode Mode; /// /// The the timer is executing in. /// public readonly TimerExecutionContext ExecutionContext; /// /// true if the timer execution is expected to take a long time. /// public readonly bool IsLongRunning; /// /// The period of the timer. /// public readonly uint Period; /// /// The initial delay time of the timer. /// public readonly uint DueTime; /// /// The delegate method that handles the timer. /// public readonly TimerCallback Callback; /// /// User-defined state information. /// public object State; internal Timer(TimerQueue queue, TimerMode mode, TimerExecutionContext context, bool isLongRunning, uint dueTime, uint period, TimerCallback callback) { Debug.Assert(queue != null); Debug.Assert(callback != null); Queue = queue; Mode = mode; ExecutionContext = context; IsLongRunning = isLongRunning; Period = period; DueTime = dueTime; Callback = callback; } internal void Dispose() { /*bool result = TimerQueue.NativeMethods.DeleteTimerQueueTimer( this.Queue._hQueue, this.Handle, new IntPtr( -1 ) ); if( result == false ) { int error = Marshal.GetLastWin32Error(); throw new Win32Exception( error ); } this.Handle = IntPtr.Zero;*/ } } #endregion /// /// A fast timer queue. /// public class TimerQueue : IDisposable { #region Interop internal delegate void WaitOrTimerDelegate(IntPtr param, [MarshalAs(UnmanagedType.Bool)] bool timerOrWaitFired); internal static class NativeMethods { [Flags] public enum TimerQueueFlags : uint { WT_EXECUTEDEFAULT = 0x00000000, WT_EXECUTEINIOTHREAD = 0x00000001, WT_EXECUTEINUITHREAD = 0x00000002, WT_EXECUTEINWAITTHREAD = 0x00000004, WT_EXECUTEONLYONCE = 0x00000008, WT_EXECUTEINTIMERTHREAD = 0x00000020, WT_EXECUTELONGFUNCTION = 0x00000010, WT_EXECUTEINPERSISTENTIOTHREAD = 0x00000040, WT_EXECUTEINPERSISTENTTHREAD = 0x00000080, } [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateTimerQueue(); /// /// /// /// A pointer to a buffer that receives a handle to the timer-queue timer on return. When this handle has expired and is no longer required, release it by calling DeleteTimerQueueTimer. /// A handle to the timer queue. This handle is returned by the CreateTimerQueue function. If this parameter is NULL, the timer is associated with the default timer queue. /// /// /// /// /// /// [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CreateTimerQueueTimer(ref IntPtr phNewTimer, IntPtr TimerQueue, WaitOrTimerDelegate Callback, IntPtr Parameter, uint DueTime, uint Period, TimerQueueFlags Flags); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ChangeTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, uint DueTime, uint Period); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DeleteTimerQueueEx(IntPtr TimerQueue, IntPtr CompletionHandle); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, IntPtr CompletionEvent); } #endregion /// /// The maximum number of timers that can be allocated at any one time. /// public const int MaximumTimers = 256; internal IntPtr _hQueue; private FastLinkedList _timers; private int _timerId; private object _syncRoot; private WaitOrTimerDelegate _delegate; /// /// Initialize a new instance. /// public TimerQueue() { _timers = new FastLinkedList(); _timerId = 0; _syncRoot = new object(); _delegate = new WaitOrTimerDelegate(this.Callback); GC.KeepAlive(_delegate); _hQueue = NativeMethods.CreateTimerQueue(); Debug.Assert(_hQueue != IntPtr.Zero); if (_hQueue == IntPtr.Zero) { int error = Marshal.GetLastWin32Error(); throw new Win32Exception(error, "Unable to create timer queue."); } } /// /// Dispose of the resources used by the timer queue. /// /// /// This just calls . /// ~TimerQueue() { Dispose(); } /// /// Dispose of the resources used by the timer queue. /// /// /// This method will wait until all currently running timers have stopped before returning. /// public void Dispose() { GC.SuppressFinalize(this); IntPtr hQueue = _hQueue; lock (_syncRoot) { _timerId = -1; _timers.Clear(); } if (hQueue != IntPtr.Zero) { // -1 indicates wait until all timers have completed bool deleted = NativeMethods.DeleteTimerQueueEx(hQueue, new IntPtr(-1)); Debug.Assert(deleted == true); } _hQueue = IntPtr.Zero; // HACK: this is to keep us alive so any pending timer callbacks will work GC.KeepAlive(this); } private Timer InternalCreateTimer(TimerMode mode, TimerExecutionContext context, TimerCallback callback, uint dueTime, uint period, bool isLongRunning) { Debug.Assert(_hQueue != IntPtr.Zero); if (_hQueue == IntPtr.Zero) throw new InvalidOperationException("The timer queue has already been disposed."); Debug.Assert(callback != null); if (callback == null) { throw new ArgumentNullException("callback"); } switch (mode) { case TimerMode.OneShot: { Debug.Assert(dueTime > 0); Debug.Assert(period == 0); if (dueTime <= 0) throw new ArgumentOutOfRangeException("dueTime", dueTime, "One-shot timers require a due time of 1ms or more."); break; } case TimerMode.Periodic: { Debug.Assert(period > 0); if (period <= 0) throw new ArgumentOutOfRangeException("period", period, "Periodic timers require a period of 1ms or more."); break; } } NativeMethods.TimerQueueFlags flags = (NativeMethods.TimerQueueFlags)((uint)mode | (uint)context); if (isLongRunning == true) flags |= NativeMethods.TimerQueueFlags.WT_EXECUTELONGFUNCTION; Timer timer = new Timer(this, mode, context, isLongRunning, dueTime, period, callback); lock (_syncRoot) { timer.Entry = _timers.Enqueue(timer); timer.ID = _timerId++; } IntPtr handle = IntPtr.Zero; bool result = NativeMethods.CreateTimerQueueTimer(ref handle, _hQueue, _delegate, new IntPtr(timer.ID), dueTime, period, flags); Debug.Assert(result == true); if (result == false) { lock (_syncRoot) _timers.Remove(timer.Entry); int error = Marshal.GetLastWin32Error(); throw new Win32Exception(error, "Unable to create timer instance."); } timer.Handle = handle; return timer; } /// /// Create a new one-shot timer that will fire at the given time. /// /// The method that will handle the timer execution. /// The time that will elapse before the timer is executed, in milliseconds. /// A new instance with the given parameters. public Timer CreateOneShotTimer(TimerCallback callback, uint dueTime) { return InternalCreateTimer(TimerMode.OneShot, TimerExecutionContext.WorkerThread, callback, dueTime, 0, false); } /// /// Create a new one-shot timer that will fire at the given time. /// /// The method that will handle the timer execution. /// The time that will elapse before the timer is executed, in milliseconds. /// The thread that the callback will execute in. /// true if the callback execution is expected to take a long time. /// A new instance with the given parameters. public Timer CreateOneShotTimer(TimerCallback callback, uint dueTime, TimerExecutionContext executionContext, bool isLongRunning) { return InternalCreateTimer(TimerMode.OneShot, executionContext, callback, dueTime, 0, isLongRunning); } /// /// Create a new periodic timer that will fire at the given interval. /// /// The method that will handle the timer execution. /// The time that will elapse between timer executions, in milliseconds. /// A new instance with the given parameters. public Timer CreatePeriodicTimer(TimerCallback callback, uint period) { return InternalCreateTimer(TimerMode.Periodic, TimerExecutionContext.WorkerThread, callback, period, period, false); } /// /// Create a new periodic timer that will fire at the given interval. /// /// The method that will handle the timer execution. /// The time that will elapse between timer executions, in milliseconds. /// The thread that the callback will execute in. /// true if the callback execution is expected to take a long time. /// A new instance with the given parameters. public Timer CreatePeriodicTimer(TimerCallback callback, uint period, TimerExecutionContext executionContext, bool isLongRunning) { return InternalCreateTimer(TimerMode.Periodic, executionContext, callback, period, period, isLongRunning); } /// /// Create a new periodic timer that will fire at the given interval after the has passed. /// /// The method that will handle the timer execution. /// The time that will elapse before the timer is first executed, in milliseconds. /// The time that will elapse between timer executions, in milliseconds. /// A new instance with the given parameters. public Timer CreatePeriodicTimer(TimerCallback callback, uint dueTime, uint period) { return InternalCreateTimer(TimerMode.Periodic, TimerExecutionContext.WorkerThread, callback, dueTime, period, false); } /// /// Create a new periodic timer that will fire at the given interval after the has passed. /// /// The method that will handle the timer execution. /// The time that will elapse before the timer is first executed, in milliseconds. /// The time that will elapse between timer executions, in milliseconds. /// The thread that the callback will execute in. /// true if the callback execution is expected to take a long time. /// A new instance with the given parameters. public Timer CreatePeriodicTimer(TimerCallback callback, uint dueTime, uint period, TimerExecutionContext executionContext, bool isLongRunning) { return InternalCreateTimer(TimerMode.Periodic, executionContext, callback, dueTime, period, isLongRunning); } /// /// Stop the given timer from executing. /// /// The to stop. /// /// This method waits until the timer, if it was currently executing, completes. /// public void StopTimer(Timer timer) { Debug.Assert(timer != null); if (timer == null) throw new ArgumentNullException("timer"); // This will kill the timer if it is about to run lock (_syncRoot) _timers.Remove(timer.Entry); timer.Dispose(); } private static int _searchCount = 0; private void Callback(IntPtr param, bool timerOrWaitFired) { int id = param.ToInt32(); // This could be faster... Timer timer = null; LinkedListEntry e = _timers.HeadEntry; while (e != null) { if (e.Value.ID == id) { timer = e.Value; if (timer.Mode == TimerMode.OneShot) { lock (_syncRoot) _timers.Remove(e); timer.Dispose(); } break; } e = e.Next; _searchCount++; } //Debug.Assert( timer != null ); if (timer == null) return; timer.Callback(timer); } } }