Showing the changes to Source/Libraries/GSF.Core.Shared/Threading (2 of 4 files). Show all
Unified · Split
| @@ -158,7 +158,8 @@ namespace GSF.Threading · 157 unmodified lines | ||
| 158 | 158 | private LogicalThreadScheduler m_scheduler; |
| 159 | 159 | private ConcurrentQueue<Action>[] m_queues; |
| 160 | 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 | 164 | private LogicalThreadStatistics m_statistics; |
| 164 | 165 | |
| @@ -170,16 +171,7 @@ namespace GSF.Threading · 5 unmodified lines | ||
| 170 | 171 | /// Creates a new instance of the <see cref="LogicalThread"/> class. |
| 171 | 172 | /// </summary> |
| 172 | 173 | public LogicalThread() |
| 173 | − : this(1) | |
| 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) | |
| 174 | + : this(DefaultScheduler) | |
| 183 | 175 | { |
| 184 | 176 | } |
| 185 | 177 | |
| @@ -187,20 +179,16 @@ namespace GSF.Threading · 1 unmodified line | ||
| 187 | 179 | /// Creates a new instance of the <see cref="LogicalThread"/> class. |
| 188 | 180 | /// </summary> |
| 189 | 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 | 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> | |
| 193 | − internal LogicalThread(LogicalThreadScheduler scheduler, int priorityLevels) | |
| 183 | + internal LogicalThread(LogicalThreadScheduler scheduler) | |
| 194 | 184 | { |
| 195 | 185 | if ((object)scheduler == null) |
| 196 | 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 | 188 | m_scheduler = scheduler; |
| 202 | − m_queues = new ConcurrentQueue<Action>[priorityLevels]; | |
| 189 | + m_queues = new ConcurrentQueue<Action>[PriorityLevels]; | |
| 203 | 190 | m_threadLocalStorage = new Dictionary<object, object>(); |
| 191 | + m_nextExecutionToken = new CancellationToken(); | |
| 204 | 192 | m_statistics = new LogicalThreadStatistics(); |
| 205 | 193 | |
| 206 | 194 | for (int i = 0; i < m_queues.Length; i++) |
| @@ -219,7 +207,7 @@ namespace GSF.Threading · 12 unmodified lines | ||
| 219 | 207 | { |
| 220 | 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 | 269 | #endregion |
| 239 | 270 | |
| 240 | 271 | #region [ Methods ] |
| @@ -260,7 +291,7 @@ namespace GSF.Threading · 19 unmodified lines | ||
| 260 | 291 | throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority)); |
| 261 | 292 | |
| 262 | 293 | m_queues[PriorityLevels - priority].Enqueue(action); |
| 263 | − m_scheduler.SignalItemHandler(this); | |
| 294 | + m_scheduler.SignalItemHandler(this, priority); | |
| 264 | 295 | } |
| 265 | 296 | |
| 266 | 297 | /// <summary> |
| @@ -305,20 +336,28 @@ namespace GSF.Threading · 38 unmodified lines | ||
| 305 | 336 | } |
| 306 | 337 | |
| 307 | 338 | /// <summary> |
| 308 | − /// Activates the thread if it is not already active. | |
| 339 | + /// Attempts to activate the thread at the given priority. | |
| 309 | 340 | /// </summary> |
| 310 | − /// <returns>True if this call to TryActivate caused the thread to be active.</returns> | |
| 311 | − internal bool TryActivate() | |
| 341 | + /// <param name="priority">The priority at which to activate the thread.</param> | |
| 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 | 352 | /// <summary> |
| 317 | − /// Deactivates the thread. | |
| 353 | + /// Completely deactivates the thread, making it available for reactivation at any priority. | |
| 318 | 354 | /// </summary> |
| 319 | 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 | 363 | /// <summary> |
| @@ -47,7 +47,7 @@ namespace GSF.Threading · 46 unmodified lines | ||
| 47 | 47 | public event EventHandler<EventArgs<Exception>> UnhandledException; |
| 48 | 48 | |
| 49 | 49 | // Fields |
| 50 | − private ConcurrentQueue<LogicalThread> m_logicalThreads; | |
| 50 | + private ConcurrentQueue<Func<LogicalThread>>[] m_logicalThreadQueues; | |
| 51 | 51 | private int m_maxThreadCount; |
| 52 | 52 | private int m_threadCount; |
| 53 | 53 | private bool m_useBackgroundThreads; |
| @@ -59,11 +59,19 @@ namespace GSF.Threading · 5 unmodified lines | ||
| 59 | 59 | /// <summary> |
| 60 | 60 | /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class. |
| 61 | 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 | 69 | m_maxThreadCount = Environment.ProcessorCount; |
| 65 | − m_logicalThreads = new ConcurrentQueue<LogicalThread>(); | |
| 70 | + m_logicalThreadQueues = new ConcurrentQueue<Func<LogicalThread>>[priorityLevels]; | |
| 66 | 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 | 77 | #endregion |
| @@ -86,7 +94,7 @@ namespace GSF.Threading · 16 unmodified lines | ||
| 86 | 94 | throw new ArgumentException("Max thread count must be greater than zero", nameof(value)); |
| 87 | 95 | |
| 88 | 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 | 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 | 134 | /// <summary> |
| 115 | 135 | /// Gets the current number of active physical threads. |
| 116 | 136 | /// </summary> |
| @@ -133,18 +153,7 @@ namespace GSF.Threading · 16 unmodified lines | ||
| 133 | 153 | /// <returns>A new logical thread managed by this scheduler.</returns> |
| 134 | 154 | public LogicalThread CreateThread() |
| 135 | 155 | { |
| 136 | − return CreateThread(1); | |
| 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); | |
| 156 | + return new LogicalThread(this); | |
| 148 | 157 | } |
| 149 | 158 | |
| 150 | 159 | /// <summary> |
| @@ -152,11 +161,12 @@ namespace GSF.Threading · 1 unmodified line | ||
| 152 | 161 | /// thread has new actions to be processed. |
| 153 | 162 | /// </summary> |
| 154 | 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 | 170 | ActivatePhysicalThread(); |
| 161 | 171 | } |
| 162 | 172 | } |
| @@ -204,13 +214,19 @@ namespace GSF.Threading · 41 unmodified lines | ||
| 204 | 214 | private void ProcessLogicalThreads() |
| 205 | 215 | { |
| 206 | 216 | Stopwatch stopwatch; |
| 217 | + Func<LogicalThread> accessor = null; | |
| 207 | 218 | LogicalThread thread; |
| 208 | 219 | Action action; |
| 209 | 220 | |
| 210 | 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 | 230 | action = thread.Pull(); |
| 215 | 231 | |
| 216 | 232 | if ((object)action != null) |
| @@ -226,17 +242,62 @@ namespace GSF.Threading · 9 unmodified lines | ||
| 226 | 242 | } |
| 227 | 243 | |
| 228 | 244 | if (thread.HasAction) |
| 229 | − m_logicalThreads.Enqueue(thread); | |
| 245 | + Enqueue(thread); | |
| 230 | 246 | else |
| 231 | 247 | thread.Deactivate(); |
| 232 | 248 | } |
| 233 | 249 | |
| 234 | 250 | DeactivatePhysicalThread(); |
| 235 | 251 | |
| 236 | − if (!m_logicalThreads.IsEmpty) | |
| 252 | + if (!m_logicalThreadQueues.All(queue => queue.IsEmpty)) | |
| 237 | 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 | 301 | /// <summary> |
| 241 | 302 | /// Attempts to execute the given action. |
| 242 | 303 | /// </summary> |