| @@ -0,0 +1,173 @@ |
| | 1 | //****************************************************************************************************** |
| | 2 | // StaticTimer.cs - Gbtc |
| | 3 | // |
| | 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. |
| | 5 | // |
| | 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See |
| | 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. |
| | 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may |
| | 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: |
| | 10 | // |
| | 11 | // http://opensource.org/licenses/MIT |
| | 12 | // |
| | 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an |
| | 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the |
| | 15 | // License for the specific language governing permissions and limitations. |
| | 16 | // |
| | 17 | // Code Modification History: |
| | 18 | // ---------------------------------------------------------------------------------------------------- |
| | 19 | // 11/10/2016 - Stephen C. Wills |
| | 20 | // Generated original version of source code. |
| | 21 | // |
| | 22 | //****************************************************************************************************** |
| | 23 | |
| | 24 | using System; |
| | 25 | using System.Collections.Concurrent; |
| | 26 | using System.Collections.Generic; |
| | 27 | |
| | 28 | namespace GSF.Threading |
| | 29 | { |
| | 30 | /// <summary> |
| | 31 | /// Represents a timer manager which registers callbacks |
| | 32 | /// with existing timers to reduce thread pool queue burden. |
| | 33 | /// </summary> |
| | 34 | public class StaticTimer |
| | 35 | { |
| | 36 | #region [ Members ] |
| | 37 | |
| | 38 | // Nested Types |
| | 39 | private class TimerAction |
| | 40 | { |
| | 41 | private WeakAction m_action; |
| | 42 | private CancellationToken m_cancellationToken; |
| | 43 | |
| | 44 | public TimerAction(Action action, CancellationToken cancellationToken) |
| | 45 | { |
| | 46 | m_action = new WeakAction(action); |
| | 47 | m_cancellationToken = cancellationToken; |
| | 48 | } |
| | 49 | |
| | 50 | public bool IsCancelled |
| | 51 | { |
| | 52 | get |
| | 53 | { |
| | 54 | return m_cancellationToken.IsCancelled; |
| | 55 | } |
| | 56 | } |
| | 57 | |
| | 58 | public bool TryInvoke() |
| | 59 | { |
| | 60 | return m_action.TryInvoke(); |
| | 61 | } |
| | 62 | } |
| | 63 | |
| | 64 | // Fields |
| | 65 | private readonly ConcurrentDictionary<int, ConcurrentQueue<TimerAction>> m_dataStreamMonitorActions; |
| | 66 | |
| | 67 | #endregion |
| | 68 | |
| | 69 | #region [ Constructors ] |
| | 70 | |
| | 71 | /// <summary> |
| | 72 | /// Creates a new instance of the <see cref="StaticTimer"/> class. |
| | 73 | /// </summary> |
| | 74 | public StaticTimer() |
| | 75 | { |
| | 76 | m_dataStreamMonitorActions = new ConcurrentDictionary<int, ConcurrentQueue<TimerAction>>(); |
| | 77 | } |
| | 78 | |
| | 79 | #endregion |
| | 80 | |
| | 81 | #region [ Methods ] |
| | 82 | |
| | 83 | /// <summary> |
| | 84 | /// Registers the given callback with the timer running at the given interval. |
| | 85 | /// </summary> |
| | 86 | /// <param name="interval">The interval at which to run the timer.</param> |
| | 87 | /// <param name="monitorAction">The action to be performed when the timer is triggered.</param> |
| | 88 | public ICancellationToken RegisterDataStreamMonitor(int interval, Action monitorAction) |
| | 89 | { |
| | 90 | CancellationToken cancellationToken = new CancellationToken(); |
| | 91 | RegisterDataStreamMonitor(interval, new TimerAction(monitorAction, cancellationToken)); |
| | 92 | return cancellationToken; |
| | 93 | } |
| | 94 | |
| | 95 | private void RegisterDataStreamMonitor(int interval, TimerAction monitorAction) |
| | 96 | { |
| | 97 | ConcurrentQueue<TimerAction> newQueue = null; |
| | 98 | ConcurrentQueue<TimerAction> queue; |
| | 99 | |
| | 100 | lock (m_dataStreamMonitorActions) |
| | 101 | { |
| | 102 | queue = m_dataStreamMonitorActions.GetOrAdd(interval, i => newQueue = new ConcurrentQueue<TimerAction>()); |
| | 103 | queue.Enqueue(monitorAction); |
| | 104 | } |
| | 105 | |
| | 106 | if (queue == newQueue) |
| | 107 | StartTimer(interval, queue); |
| | 108 | } |
| | 109 | |
| | 110 | private void StartTimer(int interval, ConcurrentQueue<TimerAction> queue) |
| | 111 | { |
| | 112 | MonitorDataStream(interval, queue, new List<TimerAction>()); |
| | 113 | } |
| | 114 | |
| | 115 | private void MonitorDataStream(int interval, ConcurrentQueue<TimerAction> queue, List<TimerAction> list) |
| | 116 | { |
| | 117 | TimerAction action; |
| | 118 | |
| | 119 | for (int i = 0; i < list.Count; i++) |
| | 120 | { |
| | 121 | action = list[i]; |
| | 122 | |
| | 123 | // False here indicates that the |
| | 124 | // action has been garbage collected |
| | 125 | if (action.IsCancelled || !action.TryInvoke()) |
| | 126 | { |
| | 127 | int end = list.Count - 1; |
| | 128 | list[i] = list[end]; |
| | 129 | list.RemoveAt(end); |
| | 130 | i--; |
| | 131 | } |
| | 132 | } |
| | 133 | |
| | 134 | // The queue is only used to populate the |
| | 135 | // list from outside the async loop |
| | 136 | while (queue.TryDequeue(out action)) |
| | 137 | list.Add(action); |
| | 138 | |
| | 139 | if (list.Count > 0) |
| | 140 | { |
| | 141 | // We still have callbacks in the list so continue the async loop |
| | 142 | new Action(() => MonitorDataStream(interval, queue, list)).DelayAndExecute(interval); |
| | 143 | } |
| | 144 | else |
| | 145 | { |
| | 146 | lock (m_dataStreamMonitorActions) |
| | 147 | { |
| | 148 | // We are retiring this async loop so we need |
| | 149 | // to remove its queue from the lookup table |
| | 150 | m_dataStreamMonitorActions.TryRemove(interval, out queue); |
| | 151 | |
| | 152 | // However, actions may have been queued up in the meantime |
| | 153 | // so we need to re-register them with a new async loop |
| | 154 | while (queue.TryDequeue(out action)) |
| | 155 | RegisterDataStreamMonitor(interval, action); |
| | 156 | } |
| | 157 | } |
| | 158 | } |
| | 159 | |
| | 160 | #endregion |
| | 161 | |
| | 162 | #region [ Static ] |
| | 163 | |
| | 164 | // Static Fields |
| | 165 | |
| | 166 | /// <summary> |
| | 167 | /// Default instance of the <see cref="StaticTimer"/> class. |
| | 168 | /// </summary> |
| | 169 | public static readonly StaticTimer Default = new StaticTimer(); |
| | 170 | |
| | 171 | #endregion |
| | 172 | } |
| | 173 | } |