Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / _TimerThread.cs / 1305376 / _TimerThread.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net { using System.Collections; using System.Globalization; using System.Threading; using System.Collections.Generic; using System.Runtime.InteropServices; ////// internal static class TimerThread { ///Acts as countdown timer, used to measure elapsed time over a [....] operation. ////// internal abstract class Queue { private readonly int m_DurationMilliseconds; internal Queue(int durationMilliseconds) { m_DurationMilliseconds = durationMilliseconds; } ///Represents a queue of timers, which all have the same duration. ////// internal int Duration { get { return m_DurationMilliseconds; } } ///The duration in milliseconds of timers in this queue. ////// internal Timer CreateTimer() { return CreateTimer(null, null); } /* // Consider removing. ///Creates and returns a handle to a new polled timer. ////// internal Timer CreateTimer(Callback callback) { return CreateTimer(callback, null); } */ ///Creates and returns a handle to a new timer. ////// internal abstract Timer CreateTimer(Callback callback, object context); } ///Creates and returns a handle to a new timer with attached context. ////// internal abstract class Timer : IDisposable { private readonly int m_StartTimeMilliseconds; private readonly int m_DurationMilliseconds; internal Timer(int durationMilliseconds) { m_DurationMilliseconds = durationMilliseconds; m_StartTimeMilliseconds = Environment.TickCount; } ///Represents a timer and provides a mechanism to cancel. ////// internal int Duration { get { return m_DurationMilliseconds; } } ///The duration in milliseconds of timer. ////// internal int StartTime { get { return m_StartTimeMilliseconds; } } ///The time (relative to Environment.TickCount) when the timer started. ////// internal int Expiration { get { return unchecked(m_StartTimeMilliseconds + m_DurationMilliseconds); } } /* // Consider removing. ///The time (relative to Environment.TickCount) when the timer will expire. ////// internal int Elapsed { get { if (HasExpired || Duration == 0) { return Duration; } int now = Environment.TickCount; if (Duration == TimeoutInfinite) { return (int) (Math.Min((uint) unchecked(now - StartTime), (uint) Int32.MaxValue); } else { return (IsTickBetween(StartTime, Expiration, now) && Duration > 1) ? (int) (Math.Min((uint) unchecked(now - StartTime), Duration - 2) : Duration - 1; } } } */ ///The amount of time the timer has been running. If it equals Duration, it has fired. 1 less means it has expired but /// not yet fired. Int32.MaxValue is the ceiling - the actual value could be longer. In the case of infinite timers, this /// value becomes unreliable when TickCount wraps (about 46 days). ////// internal int TimeRemaining { get { if (HasExpired) { return 0; } if (Duration == Timeout.Infinite) { return Timeout.Infinite; } int now = Environment.TickCount; int remaining = IsTickBetween(StartTime, Expiration, now) ? (int) (Math.Min((uint) unchecked(Expiration - now), (uint) Int32.MaxValue)) : 0; return remaining < 2 ? remaining + 1 : remaining; } } ///The amount of time left on the timer. 0 means it has fired. 1 means it has expired but /// not yet fired. -1 means infinite. Int32.MaxValue is the ceiling - the actual value could be longer. ////// internal abstract bool Cancel(); ///Cancels the timer. Returns true if the timer hasn't and won't fire; false if it has or will. ////// internal abstract bool HasExpired { get; } public void Dispose() { Cancel(); } } ///Whether or not the timer has expired. ////// internal delegate void Callback(Timer timer, int timeNoticed, object context); private const int c_ThreadIdleTimeoutMilliseconds = 30 * 1000; private const int c_CacheScanPerIterations = 32; private const int c_TickCountResolution = 15; private static LinkedListPrototype for the callback that is called when a timer expires. ///s_Queues = new LinkedList (); private static LinkedList s_NewQueues = new LinkedList (); private static int s_ThreadState = (int) TimerThreadState.Idle; // Really a TimerThreadState, but need an int for Interlocked. private static AutoResetEvent s_ThreadReadyEvent = new AutoResetEvent(false); private static ManualResetEvent s_ThreadShutdownEvent = new ManualResetEvent(false); private static WaitHandle[] s_ThreadEvents; private static int s_CacheScanIteration; private static Hashtable s_QueuesCache = new Hashtable(); static TimerThread() { s_ThreadEvents = new WaitHandle[] { s_ThreadShutdownEvent, s_ThreadReadyEvent }; AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload); } /// /// private enum TimerThreadState { Idle, Running, Stopped } ///The possible states of the timer thread. ////// internal static Queue CreateQueue(int durationMilliseconds) { if (durationMilliseconds == Timeout.Infinite) { return new InfiniteTimerQueue(); } if (durationMilliseconds < 0) { throw new ArgumentOutOfRangeException("durationMilliseconds"); } // Queues are held with a weak reference so they can simply be abandoned and automatically cleaned up. TimerQueue queue; lock(s_NewQueues) { queue = new TimerQueue(durationMilliseconds); WeakReference weakQueue = new WeakReference(queue); s_NewQueues.AddLast(weakQueue); } return queue; } ///The main external entry-point, allows creating new timer queues. ////// internal static Queue GetOrCreateQueue(int durationMilliseconds) { if (durationMilliseconds == Timeout.Infinite) { return new InfiniteTimerQueue(); } if (durationMilliseconds < 0) { throw new ArgumentOutOfRangeException("durationMilliseconds"); } TimerQueue queue; WeakReference weakQueue = (WeakReference) s_QueuesCache[durationMilliseconds]; if (weakQueue == null || (queue = (TimerQueue) weakQueue.Target) == null) { lock(s_NewQueues) { weakQueue = (WeakReference) s_QueuesCache[durationMilliseconds]; if (weakQueue == null || (queue = (TimerQueue) weakQueue.Target) == null) { queue = new TimerQueue(durationMilliseconds); weakQueue = new WeakReference(queue); s_NewQueues.AddLast(weakQueue); s_QueuesCache[durationMilliseconds] = weakQueue; // Take advantage of this lock to periodically scan the table for garbage. if (++s_CacheScanIteration % c_CacheScanPerIterations == 0) { ListAlternative cache-based queue factory. Always synchronized. ///garbage = new List (); foreach (DictionaryEntry pair in s_QueuesCache) { if (((WeakReference) pair.Value).Target == null) { garbage.Add((int) pair.Key); } } for (int i = 0; i < garbage.Count; i++) { s_QueuesCache.Remove(garbage[i]); } } } } } return queue; } /// /// private class TimerQueue : Queue { // This is a GCHandle that holds onto the TimerQueue when active timers are in it. // The TimerThread only holds WeakReferences to it so that it can be collected when the user lets go of it. // But we don't want the user to HAVE to keep a reference to it when timers are active in it. // It gets created when the first timer gets added, and cleaned up when the TimerThread notices it's empty. // The TimerThread will always notice it's empty eventually, since the TimerThread will always wake up and // try to fire the timer, even if it was cancelled and removed prematurely. private IntPtr m_ThisHandle; // This sentinel TimerNode acts as both the head and the tail, allowing nodes to go in and out of the list without updating // any TimerQueue members. m_Timers.Next is the true head, and .Prev the true tail. This also serves as the list's lock. private readonly TimerNode m_Timers; ///Represents a queue of timers of fixed duration. ////// /// internal TimerQueue(int durationMilliseconds) : base(durationMilliseconds) { // Create the doubly-linked list with a sentinel head and tail so that this member never needs updating. m_Timers = new TimerNode(); m_Timers.Next = m_Timers; m_Timers.Prev = m_Timers; // If ReleaseHandle comes back, we need something like this here. // m_HandleFrozen = s_ThreadState == (int) TimerThreadState.Stopped ? 1 : 0; } ///Create a new TimerQueue. TimerQueues must be created while s_NewQueues is locked in /// order to synchronize with Shutdown(). ////// internal override Timer CreateTimer(Callback callback, object context) { TimerNode timer = new TimerNode(callback, context, Duration, m_Timers); // Add this on the tail. (Actually, one before the tail - m_Timers is the sentinel tail.) bool needProd = false; lock (m_Timers) { GlobalLog.Assert(m_Timers.Prev.Next == m_Timers, "TimerThread#{0}::CreateTimer()|m_Tail corruption.", Thread.CurrentThread.ManagedThreadId.ToString()); // If this is the first timer in the list, we need to create a queue handle and prod the timer thread. if (m_Timers.Next == m_Timers) { if (m_ThisHandle == IntPtr.Zero) { m_ThisHandle = (IntPtr) GCHandle.Alloc(this); } needProd = true; } timer.Next = m_Timers; timer.Prev = m_Timers.Prev; m_Timers.Prev.Next = timer; m_Timers.Prev = timer; } // If, after we add the new tail, there is a chance that the tail is the next // node to be processed, we need to wake up the timer thread. if (needProd) { TimerThread.Prod(); } return timer; } ///Creates new timers. This method is thread-safe. ////// internal bool Fire(out int nextExpiration) { while (true) { // Check if we got to the end. If so, free the handle. TimerNode timer = m_Timers.Next; if (timer == m_Timers) { lock (m_Timers) { timer = m_Timers.Next; if (timer == m_Timers) { if(m_ThisHandle != IntPtr.Zero) { ((GCHandle) m_ThisHandle).Free(); m_ThisHandle = IntPtr.Zero; } nextExpiration = 0; return false; } } } if (!timer.Fire()) { nextExpiration = timer.Expiration; return true; } } } /* Currently unused. If revived, needs to be changed to the new design of m_ThisHandle. ///Called by the timer thread to fire the expired timers. Returns true if there are future timers /// in the queue, and if so, also sets nextExpiration. ////// internal void ReleaseHandle() { if (Interlocked.Exchange(ref m_HandleFrozen, 1) == 1) { return; } // Add a fake timer to the count. This will prevent the count ever again reaching zero, effectively // disabling the GCHandle alloc/free logic. If it finds that one is allocated, deallocate it. if (Interlocked.Increment(ref m_ActiveTimerCount) != 1) { IntPtr handle; while ((handle = Interlocked.Exchange(ref m_ThisHandle, IntPtr.Zero)) == IntPtr.Zero) { Thread.SpinWait(1); } ((GCHandle)handle).Free(); } } */ } ///Release the GCHandle to this object, and prevent it from ever being allocated again. ////// private class InfiniteTimerQueue : Queue { internal InfiniteTimerQueue() : base(Timeout.Infinite) { } ///A special dummy implementation for a queue of timers of infinite duration. ////// internal override Timer CreateTimer(Callback callback, object context) { return new InfiniteTimer(); } } ///Always returns a dummy infinite timer. ////// private class TimerNode : Timer { private TimerState m_TimerState; private Callback m_Callback; private object m_Context; private object m_QueueLock; private TimerNode next; private TimerNode prev; ///Internal representation of an individual timer. ////// private enum TimerState { Ready, Fired, Cancelled, Sentinel } internal TimerNode(Callback callback, object context, int durationMilliseconds, object queueLock) : base(durationMilliseconds) { if (callback != null) { m_Callback = callback; m_Context = context; } m_TimerState = TimerState.Ready; m_QueueLock = queueLock; GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::.ctor()"); } // A sentinel node - both the head and tail are one, which prevent the head and tail from ever having to be updated. internal TimerNode() : base (0) { m_TimerState = TimerState.Sentinel; } /* // Consider removing. internal bool IsDead { get { return m_TimerState != TimerState.Ready; } } */ internal override bool HasExpired { get { return m_TimerState == TimerState.Fired; } } internal TimerNode Next { get { return next; } set { next = value; } } internal TimerNode Prev { get { return prev; } set { prev = value; } } ///Status of the timer. ////// internal override bool Cancel() { if (m_TimerState == TimerState.Ready) { lock (m_QueueLock) { if (m_TimerState == TimerState.Ready) { // Remove it from the list. This keeps the list from getting to big when there are a lot of rapid creations // and cancellations. This is done before setting it to Cancelled to try to prevent the Fire() loop from // seeing it, or if it does, of having to take a lock to synchronize with the state of the list. Next.Prev = Prev; Prev.Next = Next; // Just cleanup. Doesn't need to be in the lock but is easier to have here. Next = null; Prev = null; m_Callback = null; m_Context = null; m_TimerState = TimerState.Cancelled; GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::Cancel() (success)"); return true; } } } GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::Cancel() (failure)"); return false; } ///Cancels the timer. Returns true if it hasn't and won't fire; false if it has or will, or has already been cancelled. ////// internal bool Fire() { GlobalLog.Assert(m_TimerState != TimerState.Sentinel, "TimerThread#{0}::Fire()|TimerQueue tried to Fire a Sentinel.", Thread.CurrentThread.ManagedThreadId.ToString()); if (m_TimerState != TimerState.Ready) { return true; } // Must get the current tick count within this method so it is guaranteed not to be before // StartTime, which is set in the constructor. int nowMilliseconds = Environment.TickCount; if (IsTickBetween(StartTime, Expiration, nowMilliseconds)) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Not firing (" + StartTime + " <= " + nowMilliseconds + " < " + Expiration + ")"); return false; } bool needCallback = false; lock (m_QueueLock) { if (m_TimerState == TimerState.Ready) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Firing (" + StartTime + " <= " + nowMilliseconds + " >= " + Expiration + ")"); m_TimerState = TimerState.Fired; // Remove it from the list. Next.Prev = Prev; Prev.Next = Next; // Doesn't need to be in the lock but is easier to have here. Next = null; Prev = null; needCallback = m_Callback != null; } } if (needCallback) { try { Callback callback = m_Callback; object context = m_Context; m_Callback = null; m_Context = null; callback(this, nowMilliseconds, context); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.Web, "TimerThreadTimer#" + StartTime.ToString(NumberFormatInfo.InvariantInfo) + "::Fire() - " + SR.GetString(SR.net_log_exception_in_callback, exception)); GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() exception in callback: " + exception); // This thread is not allowed to go into user code, so we should never get an exception here. // So, in debug, throw it up, killing the AppDomain. In release, we'll just ignore it. #if DEBUG throw; #endif } } return true; } } ///Fires the timer if it is still active and has expired. Returns /// true if it can be deleted, or false if it is still timing. ////// private class InfiniteTimer : Timer { internal InfiniteTimer() : base(Timeout.Infinite) { } private int cancelled; internal override bool HasExpired { get { return false; } } ///A dummy infinite timer. ////// internal override bool Cancel() { return Interlocked.Exchange(ref cancelled, 1) == 0; } } ///Cancels the timer. Returns true the first time, false after that. ////// private static void Prod() { s_ThreadReadyEvent.Set(); TimerThreadState oldState = (TimerThreadState) Interlocked.CompareExchange( ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Idle); if (oldState == TimerThreadState.Idle) { new Thread(new ThreadStart(ThreadProc)).Start(); } } ///Internal mechanism used when timers are added to wake up / create the thread. ////// private static void ThreadProc() { #if DEBUG GlobalLog.SetThreadSource(ThreadKinds.Timer); using (GlobalLog.SetThreadKind(ThreadKinds.System | ThreadKinds.Async)) { #endif GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Start"); // t_IsTimerThread = true; -- Not used anywhere. // Set this thread as a background thread. On AppDomain/Process shutdown, the thread will just be killed. Thread.CurrentThread.IsBackground = true; // Keep a permanent lock on s_Queues. This lets for example Shutdown() know when this thread isn't running. lock (s_Queues) { // If shutdown was recently called, abort here. if (Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Running) != (int) TimerThreadState.Running) { return; } bool running = true; while(running) { try { s_ThreadReadyEvent.Reset(); while (true) { // Copy all the new queues to the real queues. Since only this thread modifies the real queues, it doesn't have to lock it. if (s_NewQueues.Count > 0) { lock (s_NewQueues) { for (LinkedListNodeThread for the timer. Ignores all exceptions except ThreadAbort. If no activity occurs for a while, /// the thread will shut down. ///node = s_NewQueues.First; node != null; node = s_NewQueues.First) { s_NewQueues.Remove(node); s_Queues.AddLast(node); } } } int now = Environment.TickCount; int nextTick = 0; bool haveNextTick = false; for (LinkedListNode node = s_Queues.First; node != null; /* node = node.Next must be done in the body */) { TimerQueue queue = (TimerQueue) node.Value.Target; if (queue == null) { LinkedListNode next = node.Next; s_Queues.Remove(node); node = next; continue; } // Fire() will always return values that should be interpreted as later than 'now' (that is, even if 'now' is // returned, it is 0x100000000 milliseconds in the future). There's also a chance that Fire() will return a value // intended as > 0x100000000 milliseconds from 'now'. Either case will just cause an extra scan through the timers. int nextTickInstance; if (queue.Fire(out nextTickInstance) && (!haveNextTick || IsTickBetween(now, nextTick, nextTickInstance))){ nextTick = nextTickInstance; haveNextTick = true; } node = node.Next; } // Figure out how long to wait, taking into account how long the loop took. // Add 15 ms to compensate for poor TickCount resolution (want to guarantee a firing). int newNow = Environment.TickCount; int waitDuration = haveNextTick ? (int) (IsTickBetween(now, nextTick, newNow) ? Math.Min(unchecked((uint) (nextTick - newNow)), (uint) (Int32.MaxValue - c_TickCountResolution)) + c_TickCountResolution : 0) : c_ThreadIdleTimeoutMilliseconds; GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Waiting for " + waitDuration + "ms"); int waitResult = WaitHandle.WaitAny(s_ThreadEvents, waitDuration, false); // 0 is s_ThreadShutdownEvent - die. if (waitResult == 0) { GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Awoke, cause: Shutdown"); running = false; break; } GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Awoke, cause: " + (waitResult == WaitHandle.WaitTimeout ? "Timeout" : "Prod")); // If we timed out with nothing to do, shut down. if (waitResult == WaitHandle.WaitTimeout && !haveNextTick) { Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Idle, (int) TimerThreadState.Running); // There could have been one more prod between the wait and the exchange. Check, and abort if necessary. if (s_ThreadReadyEvent.WaitOne(0, false)) { if (Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Idle) == (int) TimerThreadState.Idle) { continue; } } running = false; break; } } } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.Web, "TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString(NumberFormatInfo.InvariantInfo) + "::ThreadProc() - Exception:" + exception.ToString()); GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() exception: " + exception); // The only options are to continue processing and likely enter an error-loop, // shut down timers for this AppDomain, or shut down the AppDomain. Go with shutting // down the AppDomain in debug, and going into a loop in retail, but try to make the // loop somewhat slow. Note that in retail, this can only be triggered by OutOfMemory or StackOverflow, // or an thrown within TimerThread - the rest are caught in Fire(). #if !DEBUG Thread.Sleep(1000); #else throw; #endif } } } GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Stop"); #if DEBUG } #endif } /* Currently unused. /// /// internal static void Shutdown() { StopTimerThread(); // As long as TimerQueues are always created and added to s_NewQueues within the same lock, // this should catch all existing TimerQueues (and all new onew will see s_ThreadState). lock (s_NewQueues) { foreach (WeakReference node in s_NewQueues) { TimerQueue queue = (TimerQueue)node.Target; if(queue != null) { queue.ReleaseHandle(); } } } // Once that thread is gone, release all the remaining GCHandles. lock (s_Queues) { foreach (WeakReference node in s_Queues) { TimerQueue queue = (TimerQueue)node.Target; if(queue != null) { queue.ReleaseHandle(); } } } } */ private static void StopTimerThread() { Interlocked.Exchange(ref s_ThreadState, (int) TimerThreadState.Stopped); s_ThreadShutdownEvent.Set(); } ///Stops the timer thread and prevents a new one from forming. No more timers can expire. ////// private static bool IsTickBetween(int start, int end, int comparand) { // Assumes that if start and end are equal, they are the same time. // Assumes that if the comparand and start are equal, no time has passed, // and that if the comparand and end are equal, end has occurred. return ((start <= comparand) == (end <= comparand)) != (start <= end); } ///Helper for deciding whether a given TickCount is before or after a given expiration /// tick count assuming that it can't be before a given starting TickCount. ////// When the AppDomain is shut down, the timer thread is stopped. ///private static void OnDomainUnload(object sender, EventArgs e) { try { StopTimerThread(); } catch { } } /* /// /// [ThreadStatic] private static bool t_IsTimerThread; // Consider removing. internal static bool IsTimerThread { get { return t_IsTimerThread; } } */ } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------------ //This thread static can be used to tell whether the current thread is the TimerThread thread. ///// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net { using System.Collections; using System.Globalization; using System.Threading; using System.Collections.Generic; using System.Runtime.InteropServices; ////// internal static class TimerThread { ///Acts as countdown timer, used to measure elapsed time over a [....] operation. ////// internal abstract class Queue { private readonly int m_DurationMilliseconds; internal Queue(int durationMilliseconds) { m_DurationMilliseconds = durationMilliseconds; } ///Represents a queue of timers, which all have the same duration. ////// internal int Duration { get { return m_DurationMilliseconds; } } ///The duration in milliseconds of timers in this queue. ////// internal Timer CreateTimer() { return CreateTimer(null, null); } /* // Consider removing. ///Creates and returns a handle to a new polled timer. ////// internal Timer CreateTimer(Callback callback) { return CreateTimer(callback, null); } */ ///Creates and returns a handle to a new timer. ////// internal abstract Timer CreateTimer(Callback callback, object context); } ///Creates and returns a handle to a new timer with attached context. ////// internal abstract class Timer : IDisposable { private readonly int m_StartTimeMilliseconds; private readonly int m_DurationMilliseconds; internal Timer(int durationMilliseconds) { m_DurationMilliseconds = durationMilliseconds; m_StartTimeMilliseconds = Environment.TickCount; } ///Represents a timer and provides a mechanism to cancel. ////// internal int Duration { get { return m_DurationMilliseconds; } } ///The duration in milliseconds of timer. ////// internal int StartTime { get { return m_StartTimeMilliseconds; } } ///The time (relative to Environment.TickCount) when the timer started. ////// internal int Expiration { get { return unchecked(m_StartTimeMilliseconds + m_DurationMilliseconds); } } /* // Consider removing. ///The time (relative to Environment.TickCount) when the timer will expire. ////// internal int Elapsed { get { if (HasExpired || Duration == 0) { return Duration; } int now = Environment.TickCount; if (Duration == TimeoutInfinite) { return (int) (Math.Min((uint) unchecked(now - StartTime), (uint) Int32.MaxValue); } else { return (IsTickBetween(StartTime, Expiration, now) && Duration > 1) ? (int) (Math.Min((uint) unchecked(now - StartTime), Duration - 2) : Duration - 1; } } } */ ///The amount of time the timer has been running. If it equals Duration, it has fired. 1 less means it has expired but /// not yet fired. Int32.MaxValue is the ceiling - the actual value could be longer. In the case of infinite timers, this /// value becomes unreliable when TickCount wraps (about 46 days). ////// internal int TimeRemaining { get { if (HasExpired) { return 0; } if (Duration == Timeout.Infinite) { return Timeout.Infinite; } int now = Environment.TickCount; int remaining = IsTickBetween(StartTime, Expiration, now) ? (int) (Math.Min((uint) unchecked(Expiration - now), (uint) Int32.MaxValue)) : 0; return remaining < 2 ? remaining + 1 : remaining; } } ///The amount of time left on the timer. 0 means it has fired. 1 means it has expired but /// not yet fired. -1 means infinite. Int32.MaxValue is the ceiling - the actual value could be longer. ////// internal abstract bool Cancel(); ///Cancels the timer. Returns true if the timer hasn't and won't fire; false if it has or will. ////// internal abstract bool HasExpired { get; } public void Dispose() { Cancel(); } } ///Whether or not the timer has expired. ////// internal delegate void Callback(Timer timer, int timeNoticed, object context); private const int c_ThreadIdleTimeoutMilliseconds = 30 * 1000; private const int c_CacheScanPerIterations = 32; private const int c_TickCountResolution = 15; private static LinkedListPrototype for the callback that is called when a timer expires. ///s_Queues = new LinkedList (); private static LinkedList s_NewQueues = new LinkedList (); private static int s_ThreadState = (int) TimerThreadState.Idle; // Really a TimerThreadState, but need an int for Interlocked. private static AutoResetEvent s_ThreadReadyEvent = new AutoResetEvent(false); private static ManualResetEvent s_ThreadShutdownEvent = new ManualResetEvent(false); private static WaitHandle[] s_ThreadEvents; private static int s_CacheScanIteration; private static Hashtable s_QueuesCache = new Hashtable(); static TimerThread() { s_ThreadEvents = new WaitHandle[] { s_ThreadShutdownEvent, s_ThreadReadyEvent }; AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload); } /// /// private enum TimerThreadState { Idle, Running, Stopped } ///The possible states of the timer thread. ////// internal static Queue CreateQueue(int durationMilliseconds) { if (durationMilliseconds == Timeout.Infinite) { return new InfiniteTimerQueue(); } if (durationMilliseconds < 0) { throw new ArgumentOutOfRangeException("durationMilliseconds"); } // Queues are held with a weak reference so they can simply be abandoned and automatically cleaned up. TimerQueue queue; lock(s_NewQueues) { queue = new TimerQueue(durationMilliseconds); WeakReference weakQueue = new WeakReference(queue); s_NewQueues.AddLast(weakQueue); } return queue; } ///The main external entry-point, allows creating new timer queues. ////// internal static Queue GetOrCreateQueue(int durationMilliseconds) { if (durationMilliseconds == Timeout.Infinite) { return new InfiniteTimerQueue(); } if (durationMilliseconds < 0) { throw new ArgumentOutOfRangeException("durationMilliseconds"); } TimerQueue queue; WeakReference weakQueue = (WeakReference) s_QueuesCache[durationMilliseconds]; if (weakQueue == null || (queue = (TimerQueue) weakQueue.Target) == null) { lock(s_NewQueues) { weakQueue = (WeakReference) s_QueuesCache[durationMilliseconds]; if (weakQueue == null || (queue = (TimerQueue) weakQueue.Target) == null) { queue = new TimerQueue(durationMilliseconds); weakQueue = new WeakReference(queue); s_NewQueues.AddLast(weakQueue); s_QueuesCache[durationMilliseconds] = weakQueue; // Take advantage of this lock to periodically scan the table for garbage. if (++s_CacheScanIteration % c_CacheScanPerIterations == 0) { ListAlternative cache-based queue factory. Always synchronized. ///garbage = new List (); foreach (DictionaryEntry pair in s_QueuesCache) { if (((WeakReference) pair.Value).Target == null) { garbage.Add((int) pair.Key); } } for (int i = 0; i < garbage.Count; i++) { s_QueuesCache.Remove(garbage[i]); } } } } } return queue; } /// /// private class TimerQueue : Queue { // This is a GCHandle that holds onto the TimerQueue when active timers are in it. // The TimerThread only holds WeakReferences to it so that it can be collected when the user lets go of it. // But we don't want the user to HAVE to keep a reference to it when timers are active in it. // It gets created when the first timer gets added, and cleaned up when the TimerThread notices it's empty. // The TimerThread will always notice it's empty eventually, since the TimerThread will always wake up and // try to fire the timer, even if it was cancelled and removed prematurely. private IntPtr m_ThisHandle; // This sentinel TimerNode acts as both the head and the tail, allowing nodes to go in and out of the list without updating // any TimerQueue members. m_Timers.Next is the true head, and .Prev the true tail. This also serves as the list's lock. private readonly TimerNode m_Timers; ///Represents a queue of timers of fixed duration. ////// /// internal TimerQueue(int durationMilliseconds) : base(durationMilliseconds) { // Create the doubly-linked list with a sentinel head and tail so that this member never needs updating. m_Timers = new TimerNode(); m_Timers.Next = m_Timers; m_Timers.Prev = m_Timers; // If ReleaseHandle comes back, we need something like this here. // m_HandleFrozen = s_ThreadState == (int) TimerThreadState.Stopped ? 1 : 0; } ///Create a new TimerQueue. TimerQueues must be created while s_NewQueues is locked in /// order to synchronize with Shutdown(). ////// internal override Timer CreateTimer(Callback callback, object context) { TimerNode timer = new TimerNode(callback, context, Duration, m_Timers); // Add this on the tail. (Actually, one before the tail - m_Timers is the sentinel tail.) bool needProd = false; lock (m_Timers) { GlobalLog.Assert(m_Timers.Prev.Next == m_Timers, "TimerThread#{0}::CreateTimer()|m_Tail corruption.", Thread.CurrentThread.ManagedThreadId.ToString()); // If this is the first timer in the list, we need to create a queue handle and prod the timer thread. if (m_Timers.Next == m_Timers) { if (m_ThisHandle == IntPtr.Zero) { m_ThisHandle = (IntPtr) GCHandle.Alloc(this); } needProd = true; } timer.Next = m_Timers; timer.Prev = m_Timers.Prev; m_Timers.Prev.Next = timer; m_Timers.Prev = timer; } // If, after we add the new tail, there is a chance that the tail is the next // node to be processed, we need to wake up the timer thread. if (needProd) { TimerThread.Prod(); } return timer; } ///Creates new timers. This method is thread-safe. ////// internal bool Fire(out int nextExpiration) { while (true) { // Check if we got to the end. If so, free the handle. TimerNode timer = m_Timers.Next; if (timer == m_Timers) { lock (m_Timers) { timer = m_Timers.Next; if (timer == m_Timers) { if(m_ThisHandle != IntPtr.Zero) { ((GCHandle) m_ThisHandle).Free(); m_ThisHandle = IntPtr.Zero; } nextExpiration = 0; return false; } } } if (!timer.Fire()) { nextExpiration = timer.Expiration; return true; } } } /* Currently unused. If revived, needs to be changed to the new design of m_ThisHandle. ///Called by the timer thread to fire the expired timers. Returns true if there are future timers /// in the queue, and if so, also sets nextExpiration. ////// internal void ReleaseHandle() { if (Interlocked.Exchange(ref m_HandleFrozen, 1) == 1) { return; } // Add a fake timer to the count. This will prevent the count ever again reaching zero, effectively // disabling the GCHandle alloc/free logic. If it finds that one is allocated, deallocate it. if (Interlocked.Increment(ref m_ActiveTimerCount) != 1) { IntPtr handle; while ((handle = Interlocked.Exchange(ref m_ThisHandle, IntPtr.Zero)) == IntPtr.Zero) { Thread.SpinWait(1); } ((GCHandle)handle).Free(); } } */ } ///Release the GCHandle to this object, and prevent it from ever being allocated again. ////// private class InfiniteTimerQueue : Queue { internal InfiniteTimerQueue() : base(Timeout.Infinite) { } ///A special dummy implementation for a queue of timers of infinite duration. ////// internal override Timer CreateTimer(Callback callback, object context) { return new InfiniteTimer(); } } ///Always returns a dummy infinite timer. ////// private class TimerNode : Timer { private TimerState m_TimerState; private Callback m_Callback; private object m_Context; private object m_QueueLock; private TimerNode next; private TimerNode prev; ///Internal representation of an individual timer. ////// private enum TimerState { Ready, Fired, Cancelled, Sentinel } internal TimerNode(Callback callback, object context, int durationMilliseconds, object queueLock) : base(durationMilliseconds) { if (callback != null) { m_Callback = callback; m_Context = context; } m_TimerState = TimerState.Ready; m_QueueLock = queueLock; GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::.ctor()"); } // A sentinel node - both the head and tail are one, which prevent the head and tail from ever having to be updated. internal TimerNode() : base (0) { m_TimerState = TimerState.Sentinel; } /* // Consider removing. internal bool IsDead { get { return m_TimerState != TimerState.Ready; } } */ internal override bool HasExpired { get { return m_TimerState == TimerState.Fired; } } internal TimerNode Next { get { return next; } set { next = value; } } internal TimerNode Prev { get { return prev; } set { prev = value; } } ///Status of the timer. ////// internal override bool Cancel() { if (m_TimerState == TimerState.Ready) { lock (m_QueueLock) { if (m_TimerState == TimerState.Ready) { // Remove it from the list. This keeps the list from getting to big when there are a lot of rapid creations // and cancellations. This is done before setting it to Cancelled to try to prevent the Fire() loop from // seeing it, or if it does, of having to take a lock to synchronize with the state of the list. Next.Prev = Prev; Prev.Next = Next; // Just cleanup. Doesn't need to be in the lock but is easier to have here. Next = null; Prev = null; m_Callback = null; m_Context = null; m_TimerState = TimerState.Cancelled; GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::Cancel() (success)"); return true; } } } GlobalLog.Print("TimerThreadTimer#" + StartTime.ToString() + "::Cancel() (failure)"); return false; } ///Cancels the timer. Returns true if it hasn't and won't fire; false if it has or will, or has already been cancelled. ////// internal bool Fire() { GlobalLog.Assert(m_TimerState != TimerState.Sentinel, "TimerThread#{0}::Fire()|TimerQueue tried to Fire a Sentinel.", Thread.CurrentThread.ManagedThreadId.ToString()); if (m_TimerState != TimerState.Ready) { return true; } // Must get the current tick count within this method so it is guaranteed not to be before // StartTime, which is set in the constructor. int nowMilliseconds = Environment.TickCount; if (IsTickBetween(StartTime, Expiration, nowMilliseconds)) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Not firing (" + StartTime + " <= " + nowMilliseconds + " < " + Expiration + ")"); return false; } bool needCallback = false; lock (m_QueueLock) { if (m_TimerState == TimerState.Ready) { GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() Firing (" + StartTime + " <= " + nowMilliseconds + " >= " + Expiration + ")"); m_TimerState = TimerState.Fired; // Remove it from the list. Next.Prev = Prev; Prev.Next = Next; // Doesn't need to be in the lock but is easier to have here. Next = null; Prev = null; needCallback = m_Callback != null; } } if (needCallback) { try { Callback callback = m_Callback; object context = m_Context; m_Callback = null; m_Context = null; callback(this, nowMilliseconds, context); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.Web, "TimerThreadTimer#" + StartTime.ToString(NumberFormatInfo.InvariantInfo) + "::Fire() - " + SR.GetString(SR.net_log_exception_in_callback, exception)); GlobalLog.Print("TimerThreadTimer#" + StartTime + "::Fire() exception in callback: " + exception); // This thread is not allowed to go into user code, so we should never get an exception here. // So, in debug, throw it up, killing the AppDomain. In release, we'll just ignore it. #if DEBUG throw; #endif } } return true; } } ///Fires the timer if it is still active and has expired. Returns /// true if it can be deleted, or false if it is still timing. ////// private class InfiniteTimer : Timer { internal InfiniteTimer() : base(Timeout.Infinite) { } private int cancelled; internal override bool HasExpired { get { return false; } } ///A dummy infinite timer. ////// internal override bool Cancel() { return Interlocked.Exchange(ref cancelled, 1) == 0; } } ///Cancels the timer. Returns true the first time, false after that. ////// private static void Prod() { s_ThreadReadyEvent.Set(); TimerThreadState oldState = (TimerThreadState) Interlocked.CompareExchange( ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Idle); if (oldState == TimerThreadState.Idle) { new Thread(new ThreadStart(ThreadProc)).Start(); } } ///Internal mechanism used when timers are added to wake up / create the thread. ////// private static void ThreadProc() { #if DEBUG GlobalLog.SetThreadSource(ThreadKinds.Timer); using (GlobalLog.SetThreadKind(ThreadKinds.System | ThreadKinds.Async)) { #endif GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Start"); // t_IsTimerThread = true; -- Not used anywhere. // Set this thread as a background thread. On AppDomain/Process shutdown, the thread will just be killed. Thread.CurrentThread.IsBackground = true; // Keep a permanent lock on s_Queues. This lets for example Shutdown() know when this thread isn't running. lock (s_Queues) { // If shutdown was recently called, abort here. if (Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Running) != (int) TimerThreadState.Running) { return; } bool running = true; while(running) { try { s_ThreadReadyEvent.Reset(); while (true) { // Copy all the new queues to the real queues. Since only this thread modifies the real queues, it doesn't have to lock it. if (s_NewQueues.Count > 0) { lock (s_NewQueues) { for (LinkedListNodeThread for the timer. Ignores all exceptions except ThreadAbort. If no activity occurs for a while, /// the thread will shut down. ///node = s_NewQueues.First; node != null; node = s_NewQueues.First) { s_NewQueues.Remove(node); s_Queues.AddLast(node); } } } int now = Environment.TickCount; int nextTick = 0; bool haveNextTick = false; for (LinkedListNode node = s_Queues.First; node != null; /* node = node.Next must be done in the body */) { TimerQueue queue = (TimerQueue) node.Value.Target; if (queue == null) { LinkedListNode next = node.Next; s_Queues.Remove(node); node = next; continue; } // Fire() will always return values that should be interpreted as later than 'now' (that is, even if 'now' is // returned, it is 0x100000000 milliseconds in the future). There's also a chance that Fire() will return a value // intended as > 0x100000000 milliseconds from 'now'. Either case will just cause an extra scan through the timers. int nextTickInstance; if (queue.Fire(out nextTickInstance) && (!haveNextTick || IsTickBetween(now, nextTick, nextTickInstance))){ nextTick = nextTickInstance; haveNextTick = true; } node = node.Next; } // Figure out how long to wait, taking into account how long the loop took. // Add 15 ms to compensate for poor TickCount resolution (want to guarantee a firing). int newNow = Environment.TickCount; int waitDuration = haveNextTick ? (int) (IsTickBetween(now, nextTick, newNow) ? Math.Min(unchecked((uint) (nextTick - newNow)), (uint) (Int32.MaxValue - c_TickCountResolution)) + c_TickCountResolution : 0) : c_ThreadIdleTimeoutMilliseconds; GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Waiting for " + waitDuration + "ms"); int waitResult = WaitHandle.WaitAny(s_ThreadEvents, waitDuration, false); // 0 is s_ThreadShutdownEvent - die. if (waitResult == 0) { GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Awoke, cause: Shutdown"); running = false; break; } GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Awoke, cause: " + (waitResult == WaitHandle.WaitTimeout ? "Timeout" : "Prod")); // If we timed out with nothing to do, shut down. if (waitResult == WaitHandle.WaitTimeout && !haveNextTick) { Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Idle, (int) TimerThreadState.Running); // There could have been one more prod between the wait and the exchange. Check, and abort if necessary. if (s_ThreadReadyEvent.WaitOne(0, false)) { if (Interlocked.CompareExchange(ref s_ThreadState, (int) TimerThreadState.Running, (int) TimerThreadState.Idle) == (int) TimerThreadState.Idle) { continue; } } running = false; break; } } } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.Web, "TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString(NumberFormatInfo.InvariantInfo) + "::ThreadProc() - Exception:" + exception.ToString()); GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() exception: " + exception); // The only options are to continue processing and likely enter an error-loop, // shut down timers for this AppDomain, or shut down the AppDomain. Go with shutting // down the AppDomain in debug, and going into a loop in retail, but try to make the // loop somewhat slow. Note that in retail, this can only be triggered by OutOfMemory or StackOverflow, // or an thrown within TimerThread - the rest are caught in Fire(). #if !DEBUG Thread.Sleep(1000); #else throw; #endif } } } GlobalLog.Print("TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString() + "::ThreadProc() Stop"); #if DEBUG } #endif } /* Currently unused. /// /// internal static void Shutdown() { StopTimerThread(); // As long as TimerQueues are always created and added to s_NewQueues within the same lock, // this should catch all existing TimerQueues (and all new onew will see s_ThreadState). lock (s_NewQueues) { foreach (WeakReference node in s_NewQueues) { TimerQueue queue = (TimerQueue)node.Target; if(queue != null) { queue.ReleaseHandle(); } } } // Once that thread is gone, release all the remaining GCHandles. lock (s_Queues) { foreach (WeakReference node in s_Queues) { TimerQueue queue = (TimerQueue)node.Target; if(queue != null) { queue.ReleaseHandle(); } } } } */ private static void StopTimerThread() { Interlocked.Exchange(ref s_ThreadState, (int) TimerThreadState.Stopped); s_ThreadShutdownEvent.Set(); } ///Stops the timer thread and prevents a new one from forming. No more timers can expire. ////// private static bool IsTickBetween(int start, int end, int comparand) { // Assumes that if start and end are equal, they are the same time. // Assumes that if the comparand and start are equal, no time has passed, // and that if the comparand and end are equal, end has occurred. return ((start <= comparand) == (end <= comparand)) != (start <= end); } ///Helper for deciding whether a given TickCount is before or after a given expiration /// tick count assuming that it can't be before a given starting TickCount. ////// When the AppDomain is shut down, the timer thread is stopped. ///private static void OnDomainUnload(object sender, EventArgs e) { try { StopTimerThread(); } catch { } } /* /// /// [ThreadStatic] private static bool t_IsTimerThread; // Consider removing. internal static bool IsTimerThread { get { return t_IsTimerThread; } } */ } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.This thread static can be used to tell whether the current thread is the TimerThread thread. ///
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- AnnotationObservableCollection.cs
- GeneralTransform2DTo3DTo2D.cs
- ProtocolsSection.cs
- MILUtilities.cs
- IntSecurity.cs
- Odbc32.cs
- TextComposition.cs
- MemoryStream.cs
- XPathNavigator.cs
- ZipIOLocalFileBlock.cs
- XsdDataContractImporter.cs
- XPathMessageFilterElement.cs
- TraceUtils.cs
- ThreadNeutralSemaphore.cs
- WindowsRebar.cs
- StorageMappingItemCollection.cs
- DropShadowEffect.cs
- RsaKeyIdentifierClause.cs
- BackgroundWorker.cs
- BooleanAnimationBase.cs
- WebHttpSecurity.cs
- FixedFindEngine.cs
- FileInfo.cs
- XmlSchemaComplexContentExtension.cs
- XappLauncher.cs
- SEHException.cs
- DataGridPagingPage.cs
- InputLanguageSource.cs
- PointLight.cs
- IgnoreDeviceFilterElement.cs
- _NegoState.cs
- CommonObjectSecurity.cs
- BinaryFormatter.cs
- HMAC.cs
- Attributes.cs
- NumericUpDownAcceleration.cs
- BaseTreeIterator.cs
- SqlHelper.cs
- DataGridViewButtonColumn.cs
- ExpressionContext.cs
- KeySplineConverter.cs
- PermissionSet.cs
- SchemaTypeEmitter.cs
- RefreshEventArgs.cs
- MbpInfo.cs
- MaterializeFromAtom.cs
- ImmutableAssemblyCacheEntry.cs
- DispatcherExceptionFilterEventArgs.cs
- StatusBarItem.cs
- MethodImplAttribute.cs
- XamlInterfaces.cs
- WrappedOptions.cs
- C14NUtil.cs
- InternalPermissions.cs
- XmlSchemaAnnotation.cs
- CellLabel.cs
- ExpressionNode.cs
- GAC.cs
- PointLightBase.cs
- Request.cs
- MatrixKeyFrameCollection.cs
- SliderAutomationPeer.cs
- CodeAttributeArgument.cs
- wmiprovider.cs
- GifBitmapEncoder.cs
- DrawingBrush.cs
- XmlToDatasetMap.cs
- ScriptResourceHandler.cs
- EventLogPermission.cs
- SharedPersonalizationStateInfo.cs
- DiagnosticsConfigurationHandler.cs
- Table.cs
- PreviewPrintController.cs
- SharedUtils.cs
- OdbcDataReader.cs
- DataBindingHandlerAttribute.cs
- TypeNameHelper.cs
- TableItemPattern.cs
- _BasicClient.cs
- EventLogPermissionAttribute.cs
- Model3D.cs
- EventLogPermissionHolder.cs
- AddIn.cs
- BindingMAnagerBase.cs
- RowType.cs
- CodeFieldReferenceExpression.cs
- MenuItem.cs
- ProfileSettings.cs
- ToolStripControlHost.cs
- Point3DIndependentAnimationStorage.cs
- NumericUpDownAcceleration.cs
- ProfilePropertySettings.cs
- VBIdentifierNameEditor.cs
- PerformanceCounterPermission.cs
- QilSortKey.cs
- XslTransformFileEditor.cs
- KnownAssembliesSet.cs
- ResourcesGenerator.cs
- FontDriver.cs
- FormatSettings.cs