283 lines · 10.9 KB · C#
RawDownload
//******************************************************************************************************//  LogicalThreadOperation.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.////****************************************************************************************************** using System;using System.Threading; namespace GSF.Threading{    /// <summary>    /// Synchronized operation that executes on a logical thread.    /// </summary>    /// <remarks>    /// This synchronized operation supports a different usage pattern than the    /// <see cref="ISynchronizedOperation"/> interface. The pattern allows for asynchronous    /// loops and signals to be passed between threads throughout the operation, but requires    /// more diligence on the user's part to handle exceptions property and signal when the    /// operation is complete. The following is an example of how to implement a simple    /// asynchronous loop between two threads using this class.    ///     /// <code>    /// LogicalThread thread1 = new LogicalThread();    /// LogicalThread thread2 = new LogicalThread();    /// LogicalThreadOperation operation = new LogicalThreadOperation(thread1, DoOperation);    ///     /// private void DoOperation()    /// {    ///     ExecuteOnThread1();    ///     thread2.Push(() => operation.ExecuteAction(() =>    ///     {    ///         ExecuteOnThread2();    ///         operation.RunIfPending();    ///     }));    /// });    /// </code>    /// </remarks>    public class LogicalThreadOperation    {        #region [ Members ]         // Constants        private const int NotRunning = 0;        private const int Running = 1;        private const int Pending = 2;         // Fields        private LogicalThread m_thread;        private Action m_action;        private int m_priority;        private int m_state;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="LogicalThreadOperation"/> class.        /// </summary>        /// <param name="thread">The thread on which to execute the operation's action.</param>        /// <param name="action">The action to be executed.</param>        public LogicalThreadOperation(LogicalThread thread, Action action)            : this(thread, action, thread.PriorityLevels)        {        }         /// <summary>        /// Creates a new instance of the <see cref="LogicalThreadOperation"/> class.        /// </summary>        /// <param name="thread">The thread on which to execute the operation's action.</param>        /// <param name="action">The action to be executed.</param>        /// <param name="priority">The priority with which the action should be executed on the logical thread.</param>        /// <exception cref="ArgumentException"><paramref name="priority"/> is outside the range between 1 and <see cref="LogicalThread.PriorityLevels"/>.</exception>        public LogicalThreadOperation(LogicalThread thread, Action action, int priority)        {            if (priority < 1 || priority > thread.PriorityLevels)                throw new ArgumentException($"Logical thread does not support priority level {priority}. Specify a priority between 1 and {thread.PriorityLevels}.", nameof(priority));             m_thread = thread;            m_action = action;            m_priority = priority;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets a value to indicate whether the        /// operation is currently executing actions.        /// </summary>        public bool IsRunning        {            get            {                return Interlocked.CompareExchange(ref m_state, NotRunning, NotRunning) != NotRunning;            }        }         /// <summary>        /// Gets a value to indiate whether the operation has an        /// additional operation that is pending execution after        /// the currently running operation has completed.        /// </summary>        public bool IsPending        {            get            {                return Interlocked.CompareExchange(ref m_state, NotRunning, NotRunning) == Pending;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Executes the action on the current thread or marks the        /// operation as pending if the operation is already running.        /// </summary>        /// <remarks>        /// When the operation is marked as pending, it will run again at the next        /// call to <see cref="RunIfPending"/>. This can be useful if an update to        /// an object's state has invalidated the operation that is currently running        /// and will therefore need to be run again.        /// </remarks>        public void RunOnce()        {            // if (m_state == NotRunning)            //     TryRunOnce();            // else if (m_state == Running)            //     m_state = Pending;             if (Interlocked.CompareExchange(ref m_state, Pending, Running) == NotRunning)                TryRunOnce();        }         /// <summary>        /// Executes the action on another thread or marks the        /// operation as pending if the operation is already running.        /// </summary>        /// <remarks>        /// When the operation is marked as pending, it will run again at the next        /// call to <see cref="RunIfPending"/>. This can be useful if an update to        /// an object's state has invalidated the operation that is currently running        /// and will therefore need to be run again.        /// </remarks>        public void RunOnceAsync()        {            // if (m_state == NotRunning)            //     TryRunOnceAsync();            // else if (m_state == Running)            //     m_state = Pending;             if (Interlocked.CompareExchange(ref m_state, Pending, Running) == NotRunning)                TryRunOnceAsync();        }         /// <summary>        /// Attempts to execute the action on the current thread.        /// Does nothing if the operation is already running.        /// </summary>        public void TryRunOnce()        {            // if (m_state == NotRunning)            // {            //     m_state = Running;            //            //     if (ExecuteAction())            //         ExecuteActionAsync();            // }             if (Interlocked.CompareExchange(ref m_state, Running, NotRunning) == NotRunning)                ExecuteAction(m_action);        }         /// <summary>        /// Attempts to execute the action on another thread.        /// Does nothing if the operation is already running.        /// </summary>        public void TryRunOnceAsync()        {            // if (m_state == NotRunning)            // {            //     m_state = Running;            //     ExecuteActionAsync();            // }             if (Interlocked.CompareExchange(ref m_state, Running, NotRunning) == NotRunning)                m_thread.Push(() => ExecuteAction(m_action));        }         /// <summary>        /// Starts the operation over at the beginning if        /// the operation is pending or sets the operation        /// state back to not running so it can run again.        /// </summary>        /// <remarks>        /// This method must be called at the end of an operation in order to set the state        /// of the operation back to running or not running so that the operation can run again.        /// The existence of this method makes this implementation different from other synchronized        /// operations in that it requires more diligence on the user's part to signal when the        /// operation is complete. In turn, this allows the user to implement complex operations        /// that may involve asynchronous loops and signaling patterns that would not be possible        /// with the <see cref="ISynchronizedOperation"/> interface.        /// </remarks>        public void RunIfPending()        {            // if (m_state == Pending)            // {            //     m_state = Running;            //     return true;            // }            // else if (m_state == Running)            // {            //     m_state = NotRunning;            // }             if (Interlocked.CompareExchange(ref m_state, NotRunning, Running) == Pending)            {                // There is no race condition here because if m_state is Pending,                // then it cannot be changed by any other line of code except this one                Interlocked.Exchange(ref m_state, Running);                m_thread.Push(() => ExecuteAction(m_action));            }        }         /// <summary>        /// Executes an action once on the current thread.        /// </summary>        /// <remarks>        /// This method provides exception handling for the action passed into this        /// method with a couple of guarantees. The first is that regardless of what        /// thread is executing the action passed into this method, the exception        /// will be raised on the thread that the logical operation runs on. The        /// second is that the RunIfPending method will be called if an exception        /// does occur in the given action.        /// </remarks>        public void ExecuteAction(Action action)        {            try            {                action();            }            catch (Exception ex)            {                if (LogicalThread.CurrentThread != m_thread)                {                    m_thread.Push(() =>                    {                        string message = $"Exception occurred while executing logical thread operation: {ex.Message}";                        throw new Exception(message, ex);                    });                }                 RunIfPending();                 if (LogicalThread.CurrentThread == m_thread)                    throw;            }        }         #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