All pastes #959865 Raw Edit

Anonymous

public text v1 · immutable
#959865 ·published 2008-03-27 18:59 UTC
rendered paste body
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);
        }
    }
}