//******************************************************************************************************
// ActionExtensions.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 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:
// ----------------------------------------------------------------------------------------------------
// 02/02/2016 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Threading;
namespace GSF.Threading
{
///
/// Defines extension methods for actions.
///
public static class ActionExtensions
{
// Cancellation token to cancel delayed operations.
private class DelayCancellationToken : ICancellationToken
{
#region [ Members ]
// Fields
private const int Idle = 0;
private const int Busy = 1;
private const int Disposed = 2;
private readonly ManualResetEvent WaitObj;
private int m_state;
#endregion
#region [ Constructors ]
public DelayCancellationToken(ManualResetEvent waitObj)
{
WaitObj = waitObj;
}
#endregion
#region [ Properties ]
public bool IsCancelled
{
get
{
if (Interlocked.CompareExchange(ref m_state, 0, 0) != Idle)
return true;
try
{
return WaitObj.WaitOne(0);
}
catch
{
return true;
}
}
}
#endregion
#region [ Methods ]
public bool Cancel()
{
// If the token is not idle, that means that either a Cancel
// operation is in progress or the token has been disposed
if (Interlocked.CompareExchange(ref m_state, Busy, Idle) != Idle)
return true;
try
{
// Determine whether the wait
// handle has already been set
if (WaitObj.WaitOne(0))
return true;
// Set the wait handle and return a value indicating
// that the token was not previously cancelled
WaitObj.Set();
return false;
}
finally
{
// Set the state back to idle and dispose of the wait
// handle if Dispose() was called while the token was busy
if (Interlocked.CompareExchange(ref m_state, Idle, Busy) == Disposed)
WaitObj.Dispose();
}
}
public void Dispose()
{
// Set state to Disposed, but do not dispose
// of the wait handle if the token is busy
if (Interlocked.Exchange(ref m_state, Disposed) != Idle)
return;
// Set the wait handle
// and dispose of it
WaitObj.Set();
WaitObj.Dispose();
}
#endregion
}
///
/// Execute an action on the thread pool after a specified number of milliseconds.
///
/// The action to be executed.
/// The wait handle to be used to cancel execution.
/// The amount of time to wait before execution, in milliseconds.
public static void DelayAndExecute(this Action action, WaitHandle waitObj, int delay)
{
object waitHandleLock = new object();
RegisteredWaitHandle waitHandle = null;
WaitOrTimerCallback callback = (state, timeout) =>
{
if (Interlocked.Exchange(ref waitHandleLock, null) == null)
waitHandle.Unregister(null);
if (!timeout)
return;
action();
};
waitHandle = ThreadPool.RegisterWaitForSingleObject(waitObj, callback, null, delay, true);
if (Interlocked.Exchange(ref waitHandleLock, null) == null)
waitHandle.Unregister(null);
}
///
/// Execute an action on the thread pool after a specified number of milliseconds.
///
/// The action to be executed.
/// The amount of time to wait before execution, in milliseconds.
/// A cancellation token that can be used to cancel the operation.
public static ICancellationToken DelayAndExecute(this Action action, int delay)
{
object waitHandleLock = new object();
ManualResetEvent waitObj = new ManualResetEvent(false);
DelayCancellationToken cancellationToken = new DelayCancellationToken(waitObj);
RegisteredWaitHandle waitHandle = null;
WaitOrTimerCallback callback = (state, timeout) =>
{
// Even if the callback timed out, another thread may cancel
// the cancellation token before we are able to dispose of it
// so we explicitly cancel the token in order to be sure
timeout = timeout && cancellationToken.Cancel();
// Both the callback thread and the caller thread will
// attempt to set the wait handle lock to null, and the
// last one to to do so has to unregister and dispose
if (Interlocked.Exchange(ref waitHandleLock, null) == null)
{
waitHandle.Unregister(null);
cancellationToken.Dispose();
}
// If we didn't time out, then the action
// was cancelled by another thread
if (!timeout)
return;
action();
};
waitHandle = ThreadPool.RegisterWaitForSingleObject(waitObj, callback, null, delay, true);
// Both the callback thread and the caller thread will
// attempt to set the wait handle lock to null, and the
// last one to to do so has to unregister and dispose
if (Interlocked.Exchange(ref waitHandleLock, null) == null)
{
waitHandle.Unregister(null);
cancellationToken.Dispose();
}
return cancellationToken;
}
}
}