//******************************************************************************************************
// 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
{
///
/// A helper class that will properly coordinate any number of s
/// from different threads.
///
///
/// This class is commonly used to properly coordinate the disposal of object.
///
/// Remarks: Adapted from GSF.Threading.ReaderWriterSpinLock
///
public class SingleOccurrenceAction
{
#region [ Members ]
static class State
{
///
/// Means that no attempt to signal this class has been made.
///
public const int Unsignaled = 0;
///
/// Means that a was called and the
/// current state variables are invalid. Spin and wait for a valid state.
///
public const int Invalid = 1;
///
/// Means that was called and the callback
/// will be called by this thread once all blockers have finished.
///
public const int RunCallbackOnSignalThread = 2;
///
/// Means that was called and
/// if there were any blockers, the last blocker will execute the callback.
/// Otherwise, the signaling thread will execute the callback
///
public const int RunCallbackByBlockerThread = 3;
///
/// Indicates that the callback is executing. Only a valid state when
/// SignalWithoutWait was called.
///
public const int RunningCallback = 5;
///
/// The completed signaled state.
///
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 ]
///
/// Gets if the signal action is currently pending.
/// This means all future calls to TryBlockAction will fail.
///
public bool IsCallbackPendingOnBlocks
{
get
{
return m_isCallbackPendingOnBlocks;
}
}
///
/// Gets if the callback is executing
///
public bool IsCallbackExecuting { get; private set; }
///
/// Gets if all actions associated with the signaling of this class
/// is complete.
///
public bool IsSignalingCompleted { get; private set; }
#endregion
#region [ Methods ]
///
/// Signals this class and waits for all blockers to exit before calling the ..
/// If called concurrently, the calling thread blocks until the completes.
/// If called from within the , the function returns immediately.
///
/// The same thread calling this method will always call the .
///
/// Only one callback will be executed by this class and it will be the first one to call
/// either or .
///
/// the callback to execute
///
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();
}
}
///
/// 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 or .
///
/// the callback to execute
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();
}
}
}
///
/// Attempts to block this class from being signaled. Be sure to call within a using block.
///
/// A bool to notify if entering the block was a success
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);
}
///
/// Attempts to block this class from being signaled.
///
/// A callback to execute if the block was successful
///
/// True if the method was executed, false otherwise.
///
public bool TryBlockAction(Action executeIfSuccessful)
{
bool success;
using (TryBlockAction(out success))
{
if (success)
{
executeIfSuccessful();
}
return success;
}
}
///
/// Attempts to block this class from being signaled.
///
/// A callback to execute if the block was successful
/// A callback to execute if the block was unsuccessful
public void BlockAction(Action executeIfSuccessful, Action executeIfFailed)
{
bool success;
using (TryBlockAction(out success))
{
if (success)
{
executeIfSuccessful();
}
else
{
executeIfFailed();
}
}
}
///
/// Attempts to block this class from being signaled.
///
/// A callback to execute if the block was successful
/// A callback to execute if the block was unsuccessful
public T BlockAction(Func executeIfSuccessful, Func executeIfFailed)
{
bool success;
using (TryBlockAction(out success))
{
if (success)
{
return executeIfSuccessful();
}
else
{
return executeIfFailed();
}
}
}
///
/// Exits the block on the reader.
///
/// Cannot exit read lock when there are no readers.
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
}
}