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
/// <summary>
/// Indicates the mode of a <see cref="Timer"/>.
/// </summary>
public enum TimerMode
{
/// <summary>
/// Indicates that the timer will execute only once.
/// </summary>
OneShot = 0x00000008,
/// <summary>
/// Indicates that the timer will execute multiple times.
/// </summary>
Periodic = 0x0,
}
/// <summary>
/// Indicates the thread that the <see cref="Timer"/> callback will be executed in.
/// </summary>
public enum TimerExecutionContext
{
/// <summary>
/// By default, the callback function is queued to a non-I/O worker thread.
/// </summary>
/// <remarks>
/// This maps to <c>WT_EXECUTEDEFAULT</c>.
/// </remarks>
Default = 0x00000000,
/// <summary>
/// Queued on a non-I/O worker thread (persistent).
/// </summary>
/// <remarks>
/// This maps to <c>WT_EXECUTEINPERSISTENTTHREAD</c>.
/// </remarks>
WorkerThread = 0x00000080,
/// <summary>
/// Queued on an I/O worker thread (use only with alertable I/O events).
/// </summary>
IOThread = 0x00000001,
/// <summary>
/// Execute in the timer thread (use only with fast routines that don't block for long).
/// </summary>
TimerThread = 0x00000020,
}
/// <summary>
/// Callback delegate for <see cref="Timer"/> handling.
/// </summary>
/// <param name="timer">The timer that was fired.</param>
public delegate void TimerCallback(Timer timer);
/// <summary>
/// An individual timer instance in a <see cref="TimerQueue"/>.
/// </summary>
public class Timer
{
internal IntPtr Handle;
internal int ID;
internal LinkedListEntry<Timer> Entry;
/// <summary>
/// The <see cref="TimerQueue"/> that owns the current timer instance.
/// </summary>
public readonly TimerQueue Queue;
/// <summary>
/// The <see cref="TimerMode"/> the timer is operating in.
/// </summary>
public readonly TimerMode Mode;
/// <summary>
/// The <see cref="TimerExecutionContext"/> the timer is executing in.
/// </summary>
public readonly TimerExecutionContext ExecutionContext;
/// <summary>
/// <c>true</c> if the timer execution is expected to take a long time.
/// </summary>
public readonly bool IsLongRunning;
/// <summary>
/// The period of the timer.
/// </summary>
public readonly uint Period;
/// <summary>
/// The initial delay time of the timer.
/// </summary>
public readonly uint DueTime;
/// <summary>
/// The delegate method that handles the timer.
/// </summary>
public readonly TimerCallback Callback;
/// <summary>
/// User-defined state information.
/// </summary>
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
/// <summary>
/// A fast timer queue.
/// </summary>
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();
/// <summary>
///
/// </summary>
/// <param name="phNewTimer">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.</param>
/// <param name="TimerQueue">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.</param>
/// <param name="Callback"></param>
/// <param name="Parameter"></param>
/// <param name="DueTime"></param>
/// <param name="Period"></param>
/// <param name="Flags"></param>
/// <returns></returns>
[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
/// <summary>
/// The maximum number of timers that can be allocated at any one time.
/// </summary>
public const int MaximumTimers = 256;
internal IntPtr _hQueue;
private FastLinkedList<Timer> _timers;
private int _timerId;
private object _syncRoot;
private WaitOrTimerDelegate _delegate;
/// <summary>
/// Initialize a new <see cref="TimerQueue"/> instance.
/// </summary>
public TimerQueue()
{
_timers = new FastLinkedList<Timer>();
_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.");
}
}
/// <summary>
/// Dispose of the resources used by the timer queue.
/// </summary>
/// <remarks>
/// This just calls <see cref="TimerQueue.Dispose"/>.
/// </remarks>
~TimerQueue()
{
Dispose();
}
/// <summary>
/// Dispose of the resources used by the timer queue.
/// </summary>
/// <remarks>
/// This method will wait until all currently running timers have stopped before returning.
/// </remarks>
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;
}
/// <summary>
/// Create a new one-shot timer that will fire at the given time.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="dueTime">The time that will elapse before the timer is executed, in milliseconds.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreateOneShotTimer(TimerCallback callback, uint dueTime)
{
return InternalCreateTimer(TimerMode.OneShot, TimerExecutionContext.WorkerThread, callback, dueTime, 0, false);
}
/// <summary>
/// Create a new one-shot timer that will fire at the given time.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="dueTime">The time that will elapse before the timer is executed, in milliseconds.</param>
/// <param name="executionContext">The thread that the callback will execute in.</param>
/// <param name="isLongRunning"><c>true</c> if the callback execution is expected to take a long time.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreateOneShotTimer(TimerCallback callback, uint dueTime, TimerExecutionContext executionContext, bool isLongRunning)
{
return InternalCreateTimer(TimerMode.OneShot, executionContext, callback, dueTime, 0, isLongRunning);
}
/// <summary>
/// Create a new periodic timer that will fire at the given interval.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="period">The time that will elapse between timer executions, in milliseconds.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreatePeriodicTimer(TimerCallback callback, uint period)
{
return InternalCreateTimer(TimerMode.Periodic, TimerExecutionContext.WorkerThread, callback, period, period, false);
}
/// <summary>
/// Create a new periodic timer that will fire at the given interval.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="period">The time that will elapse between timer executions, in milliseconds.</param>
/// <param name="executionContext">The thread that the callback will execute in.</param>
/// <param name="isLongRunning"><c>true</c> if the callback execution is expected to take a long time.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreatePeriodicTimer(TimerCallback callback, uint period, TimerExecutionContext executionContext, bool isLongRunning)
{
return InternalCreateTimer(TimerMode.Periodic, executionContext, callback, period, period, isLongRunning);
}
/// <summary>
/// Create a new periodic timer that will fire at the given interval after the <paramref name="dueTime"/> has passed.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="dueTime">The time that will elapse before the timer is first executed, in milliseconds.</param>
/// <param name="period">The time that will elapse between timer executions, in milliseconds.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreatePeriodicTimer(TimerCallback callback, uint dueTime, uint period)
{
return InternalCreateTimer(TimerMode.Periodic, TimerExecutionContext.WorkerThread, callback, dueTime, period, false);
}
/// <summary>
/// Create a new periodic timer that will fire at the given interval after the <paramref name="dueTime"/> has passed.
/// </summary>
/// <param name="callback">The method that will handle the timer execution.</param>
/// <param name="dueTime">The time that will elapse before the timer is first executed, in milliseconds.</param>
/// <param name="period">The time that will elapse between timer executions, in milliseconds.</param>
/// <param name="executionContext">The thread that the callback will execute in.</param>
/// <param name="isLongRunning"><c>true</c> if the callback execution is expected to take a long time.</param>
/// <returns>A new <see cref="Timer"/> instance with the given parameters.</returns>
public Timer CreatePeriodicTimer(TimerCallback callback, uint dueTime, uint period, TimerExecutionContext executionContext, bool isLongRunning)
{
return InternalCreateTimer(TimerMode.Periodic, executionContext, callback, dueTime, period, isLongRunning);
}
/// <summary>
/// Stop the given timer from executing.
/// </summary>
/// <param name="timer">The <see cref="Timer"/> to stop.</param>
/// <remarks>
/// This method waits until the timer, if it was currently executing, completes.
/// </remarks>
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<Timer> 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);
}
}
}