//****************************************************************************************************** // SynchronizedOperationBase.cs - Gbtc // // Copyright © 2014, 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://www.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: // ---------------------------------------------------------------------------------------------------- // 03/21/2014 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Threading; namespace GSF.Threading { /// /// Base class for operations that cannot run while they is already in progress. /// /// /// This class handles the synchronization between the methods defined in the /// interface. Implementers should only need /// to implement the method to provide a mechanism /// for executing the action on a separate thread. /// public abstract class SynchronizedOperationBase : ISynchronizedOperation { #region [ Members ] // Constants private const int NotRunning = 0; private const int Running = 1; private const int Pending = 2; // Fields private readonly Action m_action; private readonly Action m_exceptionAction; private int m_state; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// /// The action to be performed during this operation. protected SynchronizedOperationBase(Action action) : this(action, null) { } /// /// Creates a new instance of the class. /// /// The action to be performed during this operation. /// The action to be performed if an exception is thrown from the action. protected SynchronizedOperationBase(Action action, Action exceptionAction) { if ((object)action == null) throw new ArgumentNullException("action"); m_action = action; m_exceptionAction = exceptionAction; } #endregion #region [ Properties ] /// /// Gets a value to indicate whether the synchronized /// operation is currently executing its action. /// public bool IsRunning { get { return Interlocked.CompareExchange(ref m_state, NotRunning, NotRunning) != NotRunning; } } /// /// Gets a value to indiate whether the synchronized operation /// has an additional operation that is pending execution after /// the currently running action has completed. /// public bool IsPending { get { return Interlocked.CompareExchange(ref m_state, NotRunning, NotRunning) == Pending; } } #endregion #region [ Methods ] /// /// Executes the action on this thread or marks the /// operation as pending if the operation is already running. /// /// /// When the operation is marked as pending, it will run again after /// the operation that is currently running has completed. This is useful /// if an update has invalidated the operation that is currently running /// and will therefore need to be run again. /// /// This method does not guarantee that control will be returned to the /// thread that called it. If other threads continuously mark the operation as /// pending, this thread will continue to run the operation indefinitely. /// public void Run() { // if (m_state == NotRunning) // TryRun(); // else if (m_state == Running) // m_state = Pending; if (Interlocked.CompareExchange(ref m_state, Pending, Running) == NotRunning) TryRun(); } /// /// Attempts to execute the action on this thread. /// Does nothing if the operation is already running. /// /// /// This method does not guarantee that control will be returned to the thread /// that called it. If other threads continuously mark the operation as pending, /// this thread will continue to run the operation indefinitely. /// public void TryRun() { // if (m_state == NotRunning) // { // m_state = Running; // // while (ExecuteAction()) // { // } // } if (Interlocked.CompareExchange(ref m_state, Running, NotRunning) == NotRunning) { while (ExecuteAction()) { } } } /// /// Executes the action on this thread or marks the /// operation as pending if the operation is already running. /// /// /// When the operation is marked as pending, it will run again after the /// operation that is currently running has completed. This is useful if /// an update has invalidated the operation that is currently running and /// will therefore need to be run again. /// 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(); } /// /// Executes the action on another thread or marks the /// operation as pending if the operation is already running. /// /// /// When the operation is marked as pending, it will run again after the /// operation that is currently running has completed. This is useful if /// an update has invalidated the operation that is currently running and /// will therefore need to be run again. /// 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(); } /// /// Attempts to execute the action on this thread. /// Does nothing if the operation is already running. /// public void TryRunOnce() { // if (m_state == NotRunning) // { // m_state = Running; // // if (ExecuteAction()) // ExecuteActionAsync(); // } if (Interlocked.CompareExchange(ref m_state, Running, NotRunning) == NotRunning) { if (ExecuteAction()) ExecuteActionAsync(); } } /// /// Attempts to execute the action on another thread. /// Does nothing if the operation is already running. /// public void TryRunOnceAsync() { // if (m_state == NotRunning) // { // m_state = Running; // ExecuteActionAsync(); // } if (Interlocked.CompareExchange(ref m_state, Running, NotRunning) == NotRunning) ExecuteActionAsync(); } /// /// Executes the action once on the current thread. /// /// True if the action was pending and needs to run again; false otherwise. protected bool ExecuteAction() { try { m_action(); } catch (Exception ex) { try { if ((object)m_exceptionAction != null) m_exceptionAction(ex); } catch { } } // 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); return true; } return false; } /// /// Executes the action on a separate thread. /// /// /// Implementers should call on a separate thread /// and check the return value. If it returns true, that means it needs to run /// again. The following is a sample implementation using a regular dedicated /// thread. /// /// /// protected override void ExecuteActionAsync() /// { /// Thread actionThread = new Thread(() => /// { /// while (ExecuteAction()) /// { /// } /// }); /// /// actionThread.Start(); /// } /// /// protected abstract void ExecuteActionAsync(); #endregion } }