//****************************************************************************************************** // LogicalThread.cs - Gbtc // // Copyright © 2015, 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: // ---------------------------------------------------------------------------------------------------- // 09/24/2015 - Stephen C. Wills // Generated original version of source code. // 02/16/2016 - J. Ritchie Carroll // Changed default priority level to 1 - normal / lowest. // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; namespace GSF.Threading { /// /// Represents a set of statistics gathered about /// the execution time of actions on a logical thread. /// public class LogicalThreadStatistics { #region [ Members ] // Fields private TimeSpan m_maxExecutionTime; private TimeSpan m_minExecutionTime; private TimeSpan m_totalExecutionTime; private long m_executionCount; #endregion #region [ Properties ] /// /// Gets the execution time of the longest running action. /// public TimeSpan MaxExecutionTime { get { return m_maxExecutionTime; } } /// /// Gets the execution time of the shortest running action. /// public TimeSpan MinExecutionTime { get { return m_minExecutionTime; } } /// /// Gets the total execution time of all actions executed on the logical thread. /// public TimeSpan TotalExecutionTime { get { return m_totalExecutionTime; } } /// /// Gets the total number of actions executed on the logical thread. /// public long ExecutionCount { get { return m_executionCount; } } #endregion #region [ Methods ] /// /// Updates the statistics based on the execution time of a single action. /// /// The execution time of the action. internal void UpdateStatistics(TimeSpan executionTime) { if (m_executionCount == 0 || executionTime > m_maxExecutionTime) m_maxExecutionTime = executionTime; if (m_executionCount == 0 || executionTime < m_minExecutionTime) m_minExecutionTime = executionTime; m_totalExecutionTime += executionTime; m_executionCount++; } #endregion } /// /// Represents a thread of execution to which /// actions can be dispatched from other threads. /// /// /// /// This class provides a simple alternative to synchronization primitives /// such as wait handles and locks. Actions dispatched to a logical thread /// will be processed synchronously as though it was executed as consecutive /// method calls. All such actions can be dispatched from any thread in the /// system so method calls coming from multiple threads can be easily /// synchronized without locks, loops, wait handles, or timeouts. /// /// /// /// Note that the implements its own /// thread pool to execute tasks pushed to logical threads. Executing /// long-running processes or using synchronization primitives with high /// contention or long timeouts can hinder the logical thread scheduler's /// ability to schedule the actions of other logical threads. Like other /// thread pool implementations, you can mitigate this by increasing the /// maximum thread count of the logical thread scheduler, however it is /// recommended to avoid using synchronization primitives and instead /// synchronize those operations by running them as separate actions on /// the same logical thread. /// /// public class LogicalThread { #region [ Members ] // Events /// /// Handler for unhandled exceptions on the thread. /// public event EventHandler> UnhandledException; // Fields private LogicalThreadScheduler m_scheduler; private ConcurrentQueue[] m_queues; private Dictionary m_threadLocalStorage; private ICancellationToken m_nextExecutionToken; private int m_activePriority; private LogicalThreadStatistics m_statistics; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public LogicalThread() : this(DefaultScheduler) { } /// /// Creates a new instance of the class. /// /// The that created this thread. /// is null. internal LogicalThread(LogicalThreadScheduler scheduler) { if ((object)scheduler == null) throw new ArgumentNullException(nameof(scheduler)); m_scheduler = scheduler; m_queues = new ConcurrentQueue[PriorityLevels]; m_threadLocalStorage = new Dictionary(); m_nextExecutionToken = new CancellationToken(); m_statistics = new LogicalThreadStatistics(); for (int i = 0; i < m_queues.Length; i++) m_queues[i] = new ConcurrentQueue(); } #endregion #region [ Properties ] /// /// Gets the number of levels of priority /// supported by this logical thread. /// public int PriorityLevels { get { return m_scheduler.PriorityLevels; } } /// /// Gets a flag that indicates whether the logical /// thread has any unprocessed actions left in its queue. /// public bool HasAction { get { return m_queues.Any(queue => !queue.IsEmpty); } } /// /// Gets or sets the priority at which the /// logical thread is queued by the scheduler. /// internal int ActivePriority { get { return Interlocked.CompareExchange(ref m_activePriority, 0, 0); } set { Interlocked.Exchange(ref m_activePriority, value); } } /// /// Gets the priority of the next action to be processed on this logical thread. /// internal int NextPriority { get { return PriorityLevels - m_queues.TakeWhile(queue => queue.IsEmpty).Count(); } } /// /// Gets or sets the cancellation token for the next time /// the thread's actions will be executed by the scheduler. /// internal ICancellationToken NextExecutionToken { get { return Interlocked.CompareExchange(ref m_nextExecutionToken, null, null); } set { Interlocked.Exchange(ref m_nextExecutionToken, value); } } #endregion #region [ Methods ] /// /// Pushes an action to the logical thread. /// /// The action to be executed on this thread. public void Push(Action action) { Push(1, action); } /// /// Pushes an action to the logical thread. /// /// The priority the action should be given when executing actions on the thread (higher numbers have higher priority). /// The action to be executed on this thread. /// is outside the range between 1 and . public void Push(int priority, Action action) { if (priority < 1 || priority > PriorityLevels) throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority)); m_queues[PriorityLevels - priority].Enqueue(action); m_scheduler.SignalItemHandler(this, priority); } /// /// Clears all actions from the logical thread. /// public void Clear() { Action action; foreach (ConcurrentQueue queue in m_queues) { while (!queue.IsEmpty) queue.TryDequeue(out action); } } /// /// Samples the statistics, providing current statistic /// values and resetting statistic counters. /// /// The current statistic values. public LogicalThreadStatistics SampleStatistics() { return Interlocked.Exchange(ref m_statistics, new LogicalThreadStatistics()); } /// /// Pulls an action from the logical thread's internal queue to be executed on a physical thread. /// /// An action from the logical thread's internal queue. internal Action Pull() { Action action; foreach (ConcurrentQueue queue in m_queues) { if (queue.TryDequeue(out action)) return action; } return null; } /// /// Attempts to activate the thread at the given priority. /// /// The priority at which to activate the thread. /// True if the thread's priority needs to be changed to the given priority; false otherwise. internal bool TryActivate(int priority) { // Always get the execution token before the // active priority to mitigate race conditions ICancellationToken nextExecutionToken = NextExecutionToken; int activePriority = ActivePriority; return (activePriority < priority) && nextExecutionToken.Cancel(); } /// /// Completely deactivates the thread, making it available for reactivation at any priority. /// internal void Deactivate() { // Always update the active priority before the // execution token to mitigate race conditions ActivePriority = 0; NextExecutionToken = new CancellationToken(); } /// /// Returns the thread local object with the given ID. /// /// The key used to look up the thread local object. /// The value of the thread local object with the given ID. internal object GetThreadLocal(object key) { object value; m_threadLocalStorage.TryGetValue(key, out value); return value; } /// /// Sets the value of the thread local object with the given ID. /// /// The key used to look up the thread local object. /// The new value for the thread local object. internal void SetThreadLocal(object key, object value) { if (value != null) m_threadLocalStorage[key] = value; else m_threadLocalStorage.Remove(key); } /// /// Updates the statistics based on the execution time of a single action. /// /// The execution time of the action. internal void UpdateStatistics(TimeSpan executionTime) { m_statistics.UpdateStatistics(executionTime); } /// /// Raises the event. /// /// The unhandled exception. /// True if there are any handlers attached to this event; false otherwise. internal bool OnUnhandledException(Exception ex) { EventHandler> unhandledException = UnhandledException; if ((object)unhandledException == null) return false; unhandledException(this, new EventArgs(ex)); return true; } #endregion #region [ Static ] // Static Fields private static readonly LogicalThreadScheduler DefaultScheduler = new LogicalThreadScheduler(); private static readonly ThreadLocal LocalThread = new ThreadLocal(); // Static Properties /// /// Gets the logical thread that is currently executing. /// public static LogicalThread CurrentThread { get { return LocalThread.Value; } internal set { LocalThread.Value = value; } } #endregion } /// /// Defines extensions for the class. /// public static class LogicalThreadExtensions { /// /// Creates an awaitable that ensures the continuation is running on the logical thread. /// /// The thread to join. /// A context that, when awaited, runs on the logical thread. public static LogicalThreadAwaitable Join(this LogicalThread thread) => new LogicalThreadAwaitable(thread); /// /// Creates an awaitable that ensures the continuation is running on the logical thread. /// /// The thread to join. /// The priority of the continuation when completing asynchronously. /// A context that, when awaited, runs on the logical thread. public static LogicalThreadAwaitable Join(this LogicalThread thread, int priority) => new LogicalThreadAwaitable(thread, priority); /// /// Creates an awaitable that asynchronously yields /// to a new action on the logical thread when awaited. /// /// The thread to yield to. /// A context that, when awaited, will transition to a new action on the logical thread. public static LogicalThreadAwaitable Yield(this LogicalThread thread) => new LogicalThreadAwaitable(thread, forceYield: true); /// /// Creates an awaitable that asynchronously yields /// to a new action on the logical thread when awaited. /// /// The thread to yield to. /// The priority of the continuation. /// A context that, when awaited, will transition to a new action on the logical thread. public static LogicalThreadAwaitable Yield(this LogicalThread thread, int priority) => new LogicalThreadAwaitable(thread, priority, true); /// /// Provides an awaitable context for switching into a target environment. /// /// /// This type is intended for compiler use only. /// public struct LogicalThreadAwaitable { #region [ Members ] // Nested Types /// /// Provides an awaiter that switches into a target environment. /// /// /// This type is intended for compiler use only. /// public struct LogicalThreadAwaiter : INotifyCompletion { private LogicalThreadAwaitable Awaitable { get; } /// /// Creates a new instance of the class. /// /// The awaitable that spawned this awaiter. public LogicalThreadAwaiter(LogicalThreadAwaitable awaitable) => Awaitable = awaitable; /// /// Gets whether a yield is not required. /// /// /// This property is intended for compiler user rather than use directly in code. /// public bool IsCompleted => !Awaitable.ForceYield && Awaitable.Thread == LogicalThread.CurrentThread; /// /// Posts the back to the current context. /// /// The action to invoke asynchronously. /// The argument is null. public void OnCompleted(Action continuation) { if (continuation is null) throw new ArgumentNullException(nameof(continuation)); LogicalThread thread = Awaitable.Thread ?? new LogicalThread(); int priority = Awaitable.Priority; if (priority > 0) thread.Push(priority, continuation); else thread.Push(continuation); } /// /// Ends the await operation. /// public void GetResult() { } } #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// /// The thread on which to push continuations. /// The priority of continuations pushed to the thread. /// Force continuations to complete asynchronously. public LogicalThreadAwaitable(LogicalThread thread, int priority = 0, bool forceYield = false) { Thread = thread; Priority = priority; ForceYield = forceYield; } #endregion #region [ Properties ] private LogicalThread Thread { get; } private int Priority { get; } private bool ForceYield { get; } #endregion #region [ Methods ] /// /// Gets an awaiter for this . /// /// An awaiter for this awaitable. /// This method is intended for compiler user rather than use directly in code. public LogicalThreadAwaiter GetAwaiter() => new LogicalThreadAwaiter(this); #endregion } } }