//****************************************************************************************************** // 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 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: // ---------------------------------------------------------------------------------------------------- // 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; } } #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. /// public void RunOnce() { // if (m_state == NotRunning) // TryRun(); // else if (m_state == Running) // m_state = Pending; if (Interlocked.CompareExchange(ref m_state, Pending, Running) == NotRunning) TryRun(); } /// /// 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) // TryRunAsync(); // else if (m_state == Running) // m_state = Pending; if (Interlocked.CompareExchange(ref m_state, Pending, Running) == NotRunning) TryRunAsync(); } /// /// Attempts to execute the action on this thread. /// Does nothing if the operation is already running. /// public void TryRun() { // 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 TryRunAsync() { // 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 } }