Unified · Split
| @@ -158,7 +158,8 @@ namespace GSF.Threading · 157 unmodified lines | |||
| 158 | private LogicalThreadScheduler m_scheduler; | 158 | private LogicalThreadScheduler m_scheduler; |
| 159 | private ConcurrentQueue<Action>[] m_queues; | 159 | private ConcurrentQueue<Action>[] m_queues; |
| 160 | private Dictionary<object, object> m_threadLocalStorage; | 160 | private Dictionary<object, object> m_threadLocalStorage; |
| 161 | private int m_isActive; | 161 | private ICancellationToken m_nextExecutionToken; |
| 162 | private int m_activePriority; | ||
| 162 | 163 | ||
| 163 | private LogicalThreadStatistics m_statistics; | 164 | private LogicalThreadStatistics m_statistics; |
| 164 | 165 | ||
| @@ -170,16 +171,7 @@ namespace GSF.Threading · 5 unmodified lines | |||
| 170 | /// Creates a new instance of the <see cref="LogicalThread"/> class. | 171 | /// Creates a new instance of the <see cref="LogicalThread"/> class. |
| 171 | /// </summary> | 172 | /// </summary> |
| 172 | public LogicalThread() | 173 | public LogicalThread() |
| 173 | : this(1) | 174 | : this(DefaultScheduler) |
| 174 | { | ||
| 175 | } | ||
| 176 | |||
| 177 | /// <summary> | ||
| 178 | /// Creates a new instance of the <see cref="LogicalThread"/> class. | ||
| 179 | /// </summary> | ||
| 180 | /// <param name="priorityLevels">The number of levels of priority supported by this logical thread.</param> | ||
| 181 | public LogicalThread(int priorityLevels) | ||
| 182 | : this(DefaultScheduler, priorityLevels) | ||
| 183 | { | 175 | { |
| 184 | } | 176 | } |
| 185 | 177 | ||
| @@ -187,20 +179,16 @@ namespace GSF.Threading · 1 unmodified line | |||
| 187 | /// Creates a new instance of the <see cref="LogicalThread"/> class. | 179 | /// Creates a new instance of the <see cref="LogicalThread"/> class. |
| 188 | /// </summary> | 180 | /// </summary> |
| 189 | /// <param name="scheduler">The <see cref="LogicalThreadScheduler"/> that created this thread.</param> | 181 | /// <param name="scheduler">The <see cref="LogicalThreadScheduler"/> that created this thread.</param> |
| 190 | /// <param name="priorityLevels">The number of levels of priority supported by this logical thread.</param> | ||
| 191 | /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception> | 182 | /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception> |
| 192 | /// <exception cref="ArgumentException"><paramref name="priorityLevels"/> is less than or equal to zero.</exception> | 183 | internal LogicalThread(LogicalThreadScheduler scheduler) |
| 193 | internal LogicalThread(LogicalThreadScheduler scheduler, int priorityLevels) | ||
| 194 | { | 184 | { |
| 195 | if ((object)scheduler == null) | 185 | if ((object)scheduler == null) |
| 196 | throw new ArgumentNullException(nameof(scheduler)); | 186 | throw new ArgumentNullException(nameof(scheduler)); |
| 197 | 187 | ||
| 198 | if (priorityLevels <= 0) | ||
| 199 | throw new ArgumentException($"A logical thread must have at least one priority level.", nameof(priorityLevels)); | ||
| 200 | |||
| 201 | m_scheduler = scheduler; | 188 | m_scheduler = scheduler; |
| 202 | m_queues = new ConcurrentQueue<Action>[priorityLevels]; | 189 | m_queues = new ConcurrentQueue<Action>[PriorityLevels]; |
| 203 | m_threadLocalStorage = new Dictionary<object, object>(); | 190 | m_threadLocalStorage = new Dictionary<object, object>(); |
| 191 | m_nextExecutionToken = new CancellationToken(); | ||
| 204 | m_statistics = new LogicalThreadStatistics(); | 192 | m_statistics = new LogicalThreadStatistics(); |
| 205 | 193 | ||
| 206 | for (int i = 0; i < m_queues.Length; i++) | 194 | for (int i = 0; i < m_queues.Length; i++) |
| @@ -219,7 +207,7 @@ namespace GSF.Threading · 12 unmodified lines | |||
| 219 | { | 207 | { |
| 220 | get | 208 | get |
| 221 | { | 209 | { |
| 222 | return m_queues.Length; | 210 | return m_scheduler.PriorityLevels; |
| 223 | } | 211 | } |
| 224 | } | 212 | } |
| 225 | 213 | ||
| @@ -235,6 +223,49 @@ namespace GSF.Threading · 9 unmodified lines | |||
| 235 | } | 223 | } |
| 236 | } | 224 | } |
| 237 | 225 | ||
| 226 | /// <summary> | ||
| 227 | /// Gets or sets the priority at which the | ||
| 228 | /// logical thread is queued by the scheduler. | ||
| 229 | /// </summary> | ||
| 230 | internal int ActivePriority | ||
| 231 | { | ||
| 232 | get | ||
| 233 | { | ||
| 234 | return Interlocked.CompareExchange(ref m_activePriority, 0, 0); | ||
| 235 | } | ||
| 236 | set | ||
| 237 | { | ||
| 238 | Interlocked.Exchange(ref m_activePriority, value); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | /// <summary> | ||
| 243 | /// Gets the priority of the next action to be processed on this logical thread. | ||
| 244 | /// </summary> | ||
| 245 | internal int NextPriority | ||
| 246 | { | ||
| 247 | get | ||
| 248 | { | ||
| 249 | return PriorityLevels - m_queues.TakeWhile(queue => queue.IsEmpty).Count(); | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | /// <summary> | ||
| 254 | /// Gets or sets the cancellation token for the next time | ||
| 255 | /// the thread's actions will be executed by the scheduler. | ||
| 256 | /// </summary> | ||
| 257 | internal ICancellationToken NextExecutionToken | ||
| 258 | { | ||
| 259 | get | ||
| 260 | { | ||
| 261 | return Interlocked.CompareExchange(ref m_nextExecutionToken, null, null); | ||
| 262 | } | ||
| 263 | set | ||
| 264 | { | ||
| 265 | Interlocked.Exchange(ref m_nextExecutionToken, value); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 238 | #endregion | 269 | #endregion |
| 239 | 270 | ||
| 240 | #region [ Methods ] | 271 | #region [ Methods ] |
| @@ -260,7 +291,7 @@ namespace GSF.Threading · 19 unmodified lines | |||
| 260 | throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority)); | 291 | throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority)); |
| 261 | 292 | ||
| 262 | m_queues[PriorityLevels - priority].Enqueue(action); | 293 | m_queues[PriorityLevels - priority].Enqueue(action); |
| 263 | m_scheduler.SignalItemHandler(this); | 294 | m_scheduler.SignalItemHandler(this, priority); |
| 264 | } | 295 | } |
| 265 | 296 | ||
| 266 | /// <summary> | 297 | /// <summary> |
| @@ -305,20 +336,28 @@ namespace GSF.Threading · 38 unmodified lines | |||
| 305 | } | 336 | } |
| 306 | 337 | ||
| 307 | /// <summary> | 338 | /// <summary> |
| 308 | /// Activates the thread if it is not already active. | 339 | /// Attempts to activate the thread at the given priority. |
| 309 | /// </summary> | 340 | /// </summary> |
| 310 | /// <returns>True if this call to TryActivate caused the thread to be active.</returns> | 341 | /// <param name="priority">The priority at which to activate the thread.</param> |
| 311 | internal bool TryActivate() | 342 | /// <returns>True if the thread's priority needs to be changed to the given priority; false otherwise.</returns> |
| 343 | internal bool TryActivate(int priority) | ||
| 312 | { | 344 | { |
| 313 | return Interlocked.CompareExchange(ref m_isActive, 1, 0) == 0; | 345 | // Always get the execution token before the |
| 346 | // active priority to mitigate race conditions | ||
| 347 | ICancellationToken nextExecutionToken = NextExecutionToken; | ||
| 348 | int activePriority = ActivePriority; | ||
| 349 | return (activePriority < priority) && nextExecutionToken.Cancel(); | ||
| 314 | } | 350 | } |
| 315 | 351 | ||
| 316 | /// <summary> | 352 | /// <summary> |
| 317 | /// Deactivates the thread. | 353 | /// Completely deactivates the thread, making it available for reactivation at any priority. |
| 318 | /// </summary> | 354 | /// </summary> |
| 319 | internal void Deactivate() | 355 | internal void Deactivate() |
| 320 | { | 356 | { |
| 321 | Interlocked.Exchange(ref m_isActive, 0); | 357 | // Always update the active priority before the |
| 358 | // execution token to mitigate race conditions | ||
| 359 | ActivePriority = 0; | ||
| 360 | NextExecutionToken = new CancellationToken(); | ||
| 322 | } | 361 | } |
| 323 | 362 | ||
| 324 | /// <summary> | 363 | /// <summary> |
| @@ -47,7 +47,7 @@ namespace GSF.Threading · 46 unmodified lines | |||
| 47 | public event EventHandler<EventArgs<Exception>> UnhandledException; | 47 | public event EventHandler<EventArgs<Exception>> UnhandledException; |
| 48 | 48 | ||
| 49 | // Fields | 49 | // Fields |
| 50 | private ConcurrentQueue<LogicalThread> m_logicalThreads; | 50 | private ConcurrentQueue<Func<LogicalThread>>[] m_logicalThreadQueues; |
| 51 | private int m_maxThreadCount; | 51 | private int m_maxThreadCount; |
| 52 | private int m_threadCount; | 52 | private int m_threadCount; |
| 53 | private bool m_useBackgroundThreads; | 53 | private bool m_useBackgroundThreads; |
| @@ -59,11 +59,19 @@ namespace GSF.Threading · 5 unmodified lines | |||
| 59 | /// <summary> | 59 | /// <summary> |
| 60 | /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class. | 60 | /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class. |
| 61 | /// </summary> | 61 | /// </summary> |
| 62 | public LogicalThreadScheduler() | 62 | /// <param name="priorityLevels">The number of levels of priority supported by this logical thread scheduler.</param> |
| 63 | /// <exception cref="ArgumentException"><paramref name="priorityLevels"/> is less than or equal to zero.</exception> | ||
| 64 | public LogicalThreadScheduler(int priorityLevels = 1) | ||
| 63 | { | 65 | { |
| 66 | if (priorityLevels < 1) | ||
| 67 | throw new ArgumentException($"A logical thread scheduler must have at least one priority level.", nameof(priorityLevels)); | ||
| 68 | |||
| 64 | m_maxThreadCount = Environment.ProcessorCount; | 69 | m_maxThreadCount = Environment.ProcessorCount; |
| 65 | m_logicalThreads = new ConcurrentQueue<LogicalThread>(); | 70 | m_logicalThreadQueues = new ConcurrentQueue<Func<LogicalThread>>[priorityLevels]; |
| 66 | m_useBackgroundThreads = true; | 71 | m_useBackgroundThreads = true; |
| 72 | |||
| 73 | for (int i = 0; i < priorityLevels; i++) | ||
| 74 | m_logicalThreadQueues[i] = new ConcurrentQueue<Func<LogicalThread>>(); | ||
| 67 | } | 75 | } |
| 68 | 76 | ||
| 69 | #endregion | 77 | #endregion |
| @@ -86,7 +94,7 @@ namespace GSF.Threading · 16 unmodified lines | |||
| 86 | throw new ArgumentException("Max thread count must be greater than zero", nameof(value)); | 94 | throw new ArgumentException("Max thread count must be greater than zero", nameof(value)); |
| 87 | 95 | ||
| 88 | int diff = value - m_maxThreadCount; | 96 | int diff = value - m_maxThreadCount; |
| 89 | int inactiveThreads = Math.Min(diff, m_logicalThreads.Count); | 97 | int inactiveThreads = Math.Min(diff, m_logicalThreadQueues.Sum(queue => queue.Count)); |
| 90 | 98 | ||
| 91 | Interlocked.Exchange(ref m_maxThreadCount, value); | 99 | Interlocked.Exchange(ref m_maxThreadCount, value); |
| 92 | 100 | ||
| @@ -111,6 +119,18 @@ namespace GSF.Threading · 18 unmodified lines | |||
| 111 | } | 119 | } |
| 112 | } | 120 | } |
| 113 | 121 | ||
| 122 | /// <summary> | ||
| 123 | /// Gets the number of levels of priority | ||
| 124 | /// supported by this scheduler. | ||
| 125 | /// </summary> | ||
| 126 | public int PriorityLevels | ||
| 127 | { | ||
| 128 | get | ||
| 129 | { | ||
| 130 | return m_logicalThreadQueues.Length; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 114 | /// <summary> | 134 | /// <summary> |
| 115 | /// Gets the current number of active physical threads. | 135 | /// Gets the current number of active physical threads. |
| 116 | /// </summary> | 136 | /// </summary> |
| @@ -133,18 +153,7 @@ namespace GSF.Threading · 16 unmodified lines | |||
| 133 | /// <returns>A new logical thread managed by this scheduler.</returns> | 153 | /// <returns>A new logical thread managed by this scheduler.</returns> |
| 134 | public LogicalThread CreateThread() | 154 | public LogicalThread CreateThread() |
| 135 | { | 155 | { |
| 136 | return CreateThread(1); | 156 | return new LogicalThread(this); |
| 137 | } | ||
| 138 | |||
| 139 | /// <summary> | ||
| 140 | /// Creates a new logical thread whose | ||
| 141 | /// execution is managed by this scheduler. | ||
| 142 | /// </summary> | ||
| 143 | /// <param name="priorityLevels">The number of levels of priority supported by the logical thread.</param> | ||
| 144 | /// <returns>A new logical thread managed by this scheduler.</returns> | ||
| 145 | public LogicalThread CreateThread(int priorityLevels) | ||
| 146 | { | ||
| 147 | return new LogicalThread(this, priorityLevels); | ||
| 148 | } | 157 | } |
| 149 | 158 | ||
| 150 | /// <summary> | 159 | /// <summary> |
| @@ -152,11 +161,12 @@ namespace GSF.Threading · 1 unmodified line | |||
| 152 | /// thread has new actions to be processed. | 161 | /// thread has new actions to be processed. |
| 153 | /// </summary> | 162 | /// </summary> |
| 154 | /// <param name="thread">The thread with new actions to be processed.</param> | 163 | /// <param name="thread">The thread with new actions to be processed.</param> |
| 155 | internal void SignalItemHandler(LogicalThread thread) | 164 | /// <param name="priority">The priority at which the thread is being signaled.</param> |
| 165 | internal void SignalItemHandler(LogicalThread thread, int priority) | ||
| 156 | { | 166 | { |
| 157 | if (thread.TryActivate()) | 167 | if (thread.TryActivate(priority)) |
| 158 | { | 168 | { |
| 159 | m_logicalThreads.Enqueue(thread); | 169 | Enqueue(thread); |
| 160 | ActivatePhysicalThread(); | 170 | ActivatePhysicalThread(); |
| 161 | } | 171 | } |
| 162 | } | 172 | } |
| @@ -204,13 +214,19 @@ namespace GSF.Threading · 41 unmodified lines | |||
| 204 | private void ProcessLogicalThreads() | 214 | private void ProcessLogicalThreads() |
| 205 | { | 215 | { |
| 206 | Stopwatch stopwatch; | 216 | Stopwatch stopwatch; |
| 217 | Func<LogicalThread> accessor = null; | ||
| 207 | LogicalThread thread; | 218 | LogicalThread thread; |
| 208 | Action action; | 219 | Action action; |
| 209 | 220 | ||
| 210 | stopwatch = new Stopwatch(); | 221 | stopwatch = new Stopwatch(); |
| 211 | 222 | ||
| 212 | while (ThreadCount <= MaxThreadCount && m_logicalThreads.TryDequeue(out thread)) | 223 | while (ThreadCount <= MaxThreadCount && (object)m_logicalThreadQueues.FirstOrDefault(queue => queue.TryDequeue(out accessor)) != null) |
| 213 | { | 224 | { |
| 225 | thread = accessor(); | ||
| 226 | |||
| 227 | if ((object)thread == null) | ||
| 228 | continue; | ||
| 229 | |||
| 214 | action = thread.Pull(); | 230 | action = thread.Pull(); |
| 215 | 231 | ||
| 216 | if ((object)action != null) | 232 | if ((object)action != null) |
| @@ -226,17 +242,62 @@ namespace GSF.Threading · 9 unmodified lines | |||
| 226 | } | 242 | } |
| 227 | 243 | ||
| 228 | if (thread.HasAction) | 244 | if (thread.HasAction) |
| 229 | m_logicalThreads.Enqueue(thread); | 245 | Enqueue(thread); |
| 230 | else | 246 | else |
| 231 | thread.Deactivate(); | 247 | thread.Deactivate(); |
| 232 | } | 248 | } |
| 233 | 249 | ||
| 234 | DeactivatePhysicalThread(); | 250 | DeactivatePhysicalThread(); |
| 235 | 251 | ||
| 236 | if (!m_logicalThreads.IsEmpty) | 252 | if (!m_logicalThreadQueues.All(queue => queue.IsEmpty)) |
| 237 | ActivatePhysicalThread(); | 253 | ActivatePhysicalThread(); |
| 238 | } | 254 | } |
| 239 | 255 | ||
| 256 | /// <summary> | ||
| 257 | /// Queues the given thread for execution. | ||
| 258 | /// </summary> | ||
| 259 | /// <param name="thread">The thread to be queued for execution.</param> | ||
| 260 | private void Enqueue(LogicalThread thread) | ||
| 261 | { | ||
| 262 | ICancellationToken executionToken; | ||
| 263 | int activePriority; | ||
| 264 | int nextPriority; | ||
| 265 | |||
| 266 | do | ||
| 267 | { | ||
| 268 | // Create the execution token to be used in the closure | ||
| 269 | ICancellationToken nextExecutionToken = new CancellationToken(); | ||
| 270 | |||
| 271 | // Always update the thread's active priority before | ||
| 272 | // the execution token to mitigate race conditions | ||
| 273 | nextPriority = thread.NextPriority; | ||
| 274 | thread.ActivePriority = nextPriority; | ||
| 275 | thread.NextExecutionToken = nextExecutionToken; | ||
| 276 | |||
| 277 | // Now that the action can be cancelled by another thread using the | ||
| 278 | // new cancellation token, it should be safe to put it in the queue | ||
| 279 | m_logicalThreadQueues[PriorityLevels - nextPriority].Enqueue(() => | ||
| 280 | { | ||
| 281 | if (nextExecutionToken.Cancel()) | ||
| 282 | return thread; | ||
| 283 | |||
| 284 | return null; | ||
| 285 | }); | ||
| 286 | |||
| 287 | // Because enqueuing the thread is a multi-step process, we need to | ||
| 288 | // double-check in case the thread's priority changed in the meantime | ||
| 289 | activePriority = thread.ActivePriority; | ||
| 290 | nextPriority = thread.NextPriority; | ||
| 291 | |||
| 292 | // We can use the cancellation token we just created because we only | ||
| 293 | // really need to double-check the work that was done on this thread; | ||
| 294 | // in other words, if another thread changed the priority in the | ||
| 295 | // meantime, it can double-check its own work | ||
| 296 | executionToken = nextExecutionToken; | ||
| 297 | } | ||
| 298 | while (activePriority != nextPriority && executionToken.Cancel()); | ||
| 299 | } | ||
| 300 | |||
| 240 | /// <summary> | 301 | /// <summary> |
| 241 | /// Attempts to execute the given action. | 302 | /// Attempts to execute the given action. |
| 242 | /// </summary> | 303 | /// </summary> |
| @@ -776,9 +776,9 @@ namespace GSF.IO · 775 unmodified lines | |||
| 776 | 776 | ||
| 777 | m_fileWatchersLock = new object(); | 777 | m_fileWatchersLock = new object(); |
| 778 | m_fileWatchers = new List<SafeFileWatcher>(); | 778 | m_fileWatchers = new List<SafeFileWatcher>(); |
| 779 | m_threadScheduler = new LogicalThreadScheduler(); | 779 | m_threadScheduler = new LogicalThreadScheduler(2); |
| 780 | m_threadScheduler.UnhandledException += (sender, args) => OnError(args.Argument); | 780 | m_threadScheduler.UnhandledException += (sender, args) => OnError(args.Argument); |
| 781 | m_processingThread = m_threadScheduler.CreateThread(2); | 781 | m_processingThread = m_threadScheduler.CreateThread(); |
| 782 | m_watcherThread = m_threadScheduler.CreateThread(); | 782 | m_watcherThread = m_threadScheduler.CreateThread(); |
| 783 | m_fileWatchTimer = new Timer(15000); | 783 | m_fileWatchTimer = new Timer(15000); |
| 784 | m_fileWatchTimer.Elapsed += FileWatchTimer_Elapsed; | 784 | m_fileWatchTimer.Elapsed += FileWatchTimer_Elapsed; |
| @@ -235,7 +235,7 @@ namespace GSF.ServiceProcess · 234 unmodified lines | |||
| 235 | { | 235 | { |
| 236 | m_clientID = clientID; | 236 | m_clientID = clientID; |
| 237 | m_filter = new ClientFilter(); | 237 | m_filter = new ClientFilter(); |
| 238 | m_thread = serviceHelper.m_threadScheduler.CreateThread(2); | 238 | m_thread = serviceHelper.m_threadScheduler.CreateThread(); |
| 239 | m_messageCounts = new List<Tuple<DateTime, int>>(); | 239 | m_messageCounts = new List<Tuple<DateTime, int>>(); |
| 240 | m_serviceHelper = serviceHelper; | 240 | m_serviceHelper = serviceHelper; |
| 241 | } | 241 | } |
| @@ -720,9 +720,9 @@ namespace GSF.ServiceProcess · 478 unmodified lines | |||
| 720 | m_componentEnabledStates = new Dictionary<ISupportLifecycle, bool>(); | 720 | m_componentEnabledStates = new Dictionary<ISupportLifecycle, bool>(); |
| 721 | 721 | ||
| 722 | m_clientStatusUpdateLookup = new Dictionary<Guid, ClientStatusUpdateConfiguration>(); | 722 | m_clientStatusUpdateLookup = new Dictionary<Guid, ClientStatusUpdateConfiguration>(); |
| 723 | m_threadScheduler = new LogicalThreadScheduler(); | 723 | m_threadScheduler = new LogicalThreadScheduler(2); |
| 724 | m_threadScheduler.UnhandledException += LogicalThread_ProcessException; | 724 | m_threadScheduler.UnhandledException += LogicalThread_ProcessException; |
| 725 | m_statusUpdateThread = m_threadScheduler.CreateThread(2); | 725 | m_statusUpdateThread = m_threadScheduler.CreateThread(); |
| 726 | m_statusUpdateQueue = new List<StatusUpdate>(); | 726 | m_statusUpdateQueue = new List<StatusUpdate>(); |
| 727 | 727 | ||
| 728 | // Components | 728 | // Components |