//****************************************************************************************************** // StaticTimer.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 11/10/2016 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace GSF.Threading { /// /// Represents a timer manager which registers callbacks /// with existing timers to reduce thread pool queue burden. /// public class StaticTimer { #region [ Members ] // Nested Types private class TimerAction { private WeakAction m_action; private CancellationToken m_cancellationToken; public TimerAction(Action action, CancellationToken cancellationToken) { m_action = new WeakAction(action); m_cancellationToken = cancellationToken; } public bool IsCancelled { get { return m_cancellationToken.IsCancelled; } } public bool TryInvoke() { return m_action.TryInvoke(); } } // Fields private readonly ConcurrentDictionary> m_dataStreamMonitorActions; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public StaticTimer() { m_dataStreamMonitorActions = new ConcurrentDictionary>(); } #endregion #region [ Methods ] /// /// Registers the given callback with the timer running at the given interval. /// /// The interval at which to run the timer. /// The action to be performed when the timer is triggered. public ICancellationToken RegisterDataStreamMonitor(int interval, Action monitorAction) { CancellationToken cancellationToken = new CancellationToken(); RegisterDataStreamMonitor(interval, new TimerAction(monitorAction, cancellationToken)); return cancellationToken; } private void RegisterDataStreamMonitor(int interval, TimerAction monitorAction) { ConcurrentQueue newQueue = null; ConcurrentQueue queue; lock (m_dataStreamMonitorActions) { queue = m_dataStreamMonitorActions.GetOrAdd(interval, i => newQueue = new ConcurrentQueue()); queue.Enqueue(monitorAction); } if (queue == newQueue) StartTimer(interval, queue); } private void StartTimer(int interval, ConcurrentQueue queue) { MonitorDataStream(interval, queue, new List()); } private void MonitorDataStream(int interval, ConcurrentQueue queue, List list) { TimerAction action; for (int i = 0; i < list.Count; i++) { action = list[i]; // False here indicates that the // action has been garbage collected if (action.IsCancelled || !action.TryInvoke()) { int end = list.Count - 1; list[i] = list[end]; list.RemoveAt(end); i--; } } // The queue is only used to populate the // list from outside the async loop while (queue.TryDequeue(out action)) list.Add(action); if (list.Count > 0) { // We still have callbacks in the list so continue the async loop new Action(() => MonitorDataStream(interval, queue, list)).DelayAndExecute(interval); } else { lock (m_dataStreamMonitorActions) { // We are retiring this async loop so we need // to remove its queue from the lookup table m_dataStreamMonitorActions.TryRemove(interval, out queue); // However, actions may have been queued up in the meantime // so we need to re-register them with a new async loop while (queue.TryDequeue(out action)) RegisterDataStreamMonitor(interval, action); } } } #endregion #region [ Static ] // Static Fields /// /// Default instance of the class. /// public static readonly StaticTimer Default = new StaticTimer(); #endregion } }