315 lines · 11.8 KB · C#
RawDownload
//******************************************************************************************************//  SingleOccurrenceAction.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 Eclipse Public License -v 1.0 (the "License"); you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/eclipse-1.0.php////  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://  ----------------------------------------------------------------------------------------------------//  10/24/2016 - Steven E. Chisholm//       Generated original version of source code. //       ////****************************************************************************************************** using System;using System.Threading; #pragma warning disable 420 namespace GSF.Threading{    /// <summary>    /// A helper class that will properly coordinate any number of <see cref="Action"/>s    /// from different threads.    /// </summary>    /// <remarks>    /// This class is commonly used to properly coordinate the disposal of object.    ///     /// Remarks: Adapted from GSF.Threading.ReaderWriterSpinLock    /// </remarks>    public class SingleOccurrenceAction    {        #region [ Members ]         static class State        {            /// <summary>            /// Means that no attempt to signal this class has been made.            /// </summary>            public const int Unsignaled = 0;            /// <summary>            /// Means that a <see cref="SingleOccurrenceAction.ExecuteWithoutWait"/> was called and the             /// current state variables are invalid. Spin and wait for a valid state.            /// </summary>            public const int Invalid = 1;            /// <summary>            /// Means that <see cref="SingleOccurrenceAction.ExecuteAndWait"/> was called and the callback            /// will be called by this thread once all blockers have finished.            /// </summary>            public const int RunCallbackOnSignalThread = 2;            /// <summary>            /// Means that <see cref="SingleOccurrenceAction.ExecuteWithoutWait"/> was called and            /// if there were any blockers, the last blocker will execute the callback.            /// Otherwise, the signaling thread will execute the callback            /// </summary>            public const int RunCallbackByBlockerThread = 3;            /// <summary>            /// Indicates that the callback is executing. Only a valid state when            /// SignalWithoutWait was called.            /// </summary>            public const int RunningCallback = 5;            /// <summary>            /// The completed signaled state.            /// </summary>            public const int Signaled = 6;        }         // Fields        private StateMachine m_state = new StateMachine(State.Unsignaled);        private volatile int m_blockerCount = 0;        private volatile Action m_callback;        private volatile bool m_isCallbackPendingOnBlocks = false;        private volatile int m_callbackThreadId = -1;         #endregion         #region [ Properties ]         /// <summary>        /// Gets if the signal action is currently pending.         /// This means all future calls to TryBlockAction will fail.        /// </summary>        public bool IsCallbackPendingOnBlocks        {            get            {                return m_isCallbackPendingOnBlocks;            }        }          /// <summary>        /// Gets if the callback is executing        /// </summary>        public bool IsCallbackExecuting { get; private set; }         /// <summary>        /// Gets if all actions associated with the signaling of this class        /// is complete.        /// </summary>        public bool IsSignalingCompleted { get; private set; }         #endregion         #region [ Methods ]          /// <summary>        /// Signals this class and waits for all blockers to exit before calling the <see param="callback"/>..        /// If called concurrently, the calling thread blocks until the <see param="callback"/> completes.        /// If called from within the <see param="callback"/>, the function returns immediately.        ///         /// The same thread calling this method will always call the <see param="callback"/>.        ///         /// Only one callback will be executed by this class and it will be the first one to call         /// either <see cref="ExecuteWithoutWait"/> or <see cref="ExecuteAndWait"/>.        /// </summary>        /// <param name="callback">the callback to execute</param>        /// <returns></returns>        public void ExecuteAndWait(Action callback = null)        {            m_isCallbackPendingOnBlocks = true;            SpinWait sw = new SpinWait();            if (m_state.TryChangeState(State.Unsignaled, State.RunCallbackOnSignalThread))            {                // We now prevent any additional blocks from occuring in this class                // Wait for all blockers to exit.                while (m_blockerCount != 0) //A race condition that exists here is handled in TryEnterSignalBlock class.                {                    sw.SpinOnce();                }                 m_callbackThreadId = Thread.CurrentThread.ManagedThreadId;                IsCallbackExecuting = true;                if (callback != null)                    callback();                IsSignalingCompleted = true;                m_state.SetState(State.Signaled);                return;            }            if (Thread.CurrentThread.ManagedThreadId == m_callbackThreadId)                return;            while (m_state != State.Signaled)            {                sw.SpinOnce();            }        }          /// <summary>        /// Registers this callback to occur once all blocking call complete.        /// This function will return immediately and the callback may or may not be run.        ///         /// Only one callback will be executed by this class and it will be the first one to call         /// either <see cref="ExecuteWithoutWait"/> or <see cref="ExecuteAndWait"/>.        /// </summary>        /// <param name="callback">the callback to execute</param>        public void ExecuteWithoutWait(Action callback = null)        {            m_isCallbackPendingOnBlocks = true;            if (m_state.TryChangeState(State.Unsignaled, State.Invalid))            {                m_callback = callback;                m_state.SetState(State.RunCallbackByBlockerThread);                 if (m_blockerCount == 0)                {                    RaceForCallback();                }            }        }         /// <summary>        /// Attempts to block this class from being signaled. Be sure to call within a using block.        /// </summary>        /// <param name="success">A bool to notify if entering the block was a success</param>        public DisposableCallback TryBlockAction(out bool success)        {            if (!m_isCallbackPendingOnBlocks)            {                Interlocked.Increment(ref m_blockerCount);                if (!m_isCallbackPendingOnBlocks)                {                    success = true;                    return new DisposableCallback(ExitSignalBlock);                }                ExitSignalBlock();            }            success = false;            return default(DisposableCallback);        }         /// <summary>        /// Attempts to block this class from being signaled.         /// </summary>        /// <param name="executeIfSuccessful">A callback to execute if the block was successful</param>        /// <returns>        /// True if the method was executed, false otherwise.        /// </returns>        public bool TryBlockAction(Action executeIfSuccessful)        {            bool success;            using (TryBlockAction(out success))            {                if (success)                {                    executeIfSuccessful();                }                return success;            }        }         /// <summary>        /// Attempts to block this class from being signaled.         /// </summary>        /// <param name="executeIfSuccessful">A callback to execute if the block was successful</param>        /// <param name="executeIfFailed">A callback to execute if the block was unsuccessful</param>        public void BlockAction(Action executeIfSuccessful, Action executeIfFailed)        {            bool success;            using (TryBlockAction(out success))            {                if (success)                {                    executeIfSuccessful();                }                else                {                    executeIfFailed();                }            }        }         /// <summary>        /// Attempts to block this class from being signaled.         /// </summary>        /// <param name="executeIfSuccessful">A callback to execute if the block was successful</param>        /// <param name="executeIfFailed">A callback to execute if the block was unsuccessful</param>        public T BlockAction<T>(Func<T> executeIfSuccessful, Func<T> executeIfFailed)        {            bool success;            using (TryBlockAction(out success))            {                if (success)                {                    return executeIfSuccessful();                }                else                {                    return executeIfFailed();                }            }        }         /// <summary>        /// Exits the block on the reader.        /// </summary>        /// <exception cref="InvalidOperationException">Cannot exit read lock when there are no readers.</exception>        void ExitSignalBlock()        {            //If I'm the last blocker and there is a signal pending, I need to do better coordination.            if (Interlocked.Decrement(ref m_blockerCount) == 0 && m_isCallbackPendingOnBlocks)            {                RaceForCallback();            }        }         void RaceForCallback()        {            SpinWait sw = new SpinWait();            while (true)            {                switch (m_state)                {                    case State.Unsignaled:                    case State.Invalid:                        break;                    case State.RunCallbackByBlockerThread:                        if (m_state.TryChangeState(State.RunCallbackByBlockerThread, State.RunningCallback))                        {                            m_callbackThreadId = Thread.CurrentThread.ManagedThreadId;                            IsCallbackExecuting = true;                            if (m_callback != null)                                m_callback();                            m_callback = null;                            IsSignalingCompleted = true;                            m_state.SetState(State.Signaled);                            return;                        }                        break;                    case State.RunningCallback:                    case State.Signaled:                    case State.RunCallbackOnSignalThread:                        return;                    default:                        throw new Exception("Unknown State");                }                sw.SpinOnce();            }        }         #endregion    }} 

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t