//******************************************************************************************************// ScheduledTask.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/08/2014 - Steven E. Chisholm// Generated original version of source code. ////****************************************************************************************************** //------------------------------------------------------------------------------------------------------// WARNING: This class contains very low-level logic and is optimized to have minimal locking. Before// making any changes, be sure to consult the author as any bugs can introduce a race// condition that will be very difficult to detect and fix. Additional desired functionality// should likely result in another class being created rather than modifying this one.//------------------------------------------------------------------------------------------------------ using System;using System.Diagnostics;using System.Diagnostics.CodeAnalysis;using System.Runtime.CompilerServices;using System.Threading; namespace GSF.Threading{ #region [ Enumerations ] /// <summary> /// Specifies the threading mode to use for the <see cref="ScheduledTask"/> /// </summary> public enum ThreadingMode { /// <summary> /// A dedicated thread that is a foreground thread. /// </summary> DedicatedForeground, /// <summary> /// A dedicated thread that is a background thread. /// </summary> DedicatedBackground, /// <summary> /// A background thread from the thread pool. /// </summary> ThreadPool } /// <summary> /// Metadata about why this worker was called. /// </summary> public enum ScheduledTaskRunningReason { /// <summary> /// A normal run was scheduled. /// </summary> Running, /// <summary> /// Dispose was called and execution will terminate after this function call. /// </summary> Disposing, } #endregion /// <summary> /// Represents a way to schedule a task to be executed on a separate thread immediately or after a given time delay. /// </summary> [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] public class ScheduledTask : IDisposable { #region [ Members ] // Events /// <summary> /// Occurs every time the task should run. /// </summary> public event EventHandler<EventArgs<ScheduledTaskRunningReason>> Running; /// <summary> /// Occurs right before this task is disposed. /// </summary> public event EventHandler Disposing; /// <summary> /// Occurs when <see cref="Running"/> or <see cref="Disposing"/> event throws an exception. /// </summary> public event EventHandler<EventArgs<Exception>> UnhandledException; // Fields private int m_workerThreadID; private readonly ThreadContainerBase m_thread; // This cannot be null as it would cause duplicate calls to Start to throw a null reference exception private object m_weakCallbackToken; // A strong reference to the callback private ManualResetEvent m_waitForDispose; private readonly object m_disposeSync; private volatile bool m_disposing; #endregion #region [ Constructors ] /// <summary> /// Creates a <see cref="ScheduledTask"/>. /// </summary> /// <param name="threadMode">The manner in which the scheduled task executes.</param> /// <param name="priority">The thread priority to assign if a dedicated thread is used. This is ignored if using the thread-pool.</param> public ScheduledTask(ThreadingMode threadMode = ThreadingMode.ThreadPool, ThreadPriority priority = ThreadPriority.Normal) { m_workerThreadID = -1; m_waitForDispose = new ManualResetEvent(false); m_disposeSync = new object(); switch (threadMode) { case ThreadingMode.DedicatedForeground: m_thread = new ThreadContainerDedicated(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken), false, priority); break; case ThreadingMode.DedicatedBackground: m_thread = new ThreadContainerDedicated(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken), true, priority); break; case ThreadingMode.ThreadPool: m_thread = new ThreadContainerThreadpool(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken)); break; default: throw new ArgumentOutOfRangeException("threadMode"); } } /// <summary> /// Cleans up the <see cref="ThreadContainerBase"/> thread since that class likely will never be garbage collected. /// </summary> [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] ~ScheduledTask() { //By starting the thread inside the finalizer, the ThreadContainer will exit because its weak reference will be set to null. m_disposing = true; Thread.MemoryBarrier(); m_thread.Start(); } #endregion #region [ Methods ] /// <summary> /// Starts the task immediately, or if one was scheduled, starts the scheduled task immediately /// </summary> /// <remarks> /// <para> /// If this is called after a <see cref="Start(int)"/> the timer will be canceled /// and the process will still start immediately. /// </para> /// <para> /// This method is safe to call from any thread, including the worker thread. /// If disposed, this method will no nothing. /// </para> /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start() { m_thread.Start(); } /// <summary> /// Starts a timer to run the task after a provided interval. /// </summary> /// <param name="delay">the delay in milliseconds before the task should run</param> /// <remarks> /// <para> /// If a timer is currently pending, this function will do nothing. Do not use this /// function to reset or restart an existing timer. /// </para> /// <para> /// If called while working, a subsequent timer will be scheduled, but delay will not /// start until after the worker has completed. /// </para> /// <para> /// This method is safe to call from any thread, including the worker thread. /// If disposed, this method will no nothing. /// </para> /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(int delay) { m_thread.Start(delay); } /// <summary> /// Starts the disposing process of exiting the worker thread. /// </summary> /// <remarks> /// <para>Callback will be invoked one more time. Duplicate calls are ignored.</para> /// <para> /// Unless called from the worker thread, this method will block until the dispose /// has successfully completed. /// </para> /// </remarks> [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly"), SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")] public void Dispose() { m_disposing = true; Thread.MemoryBarrier(); m_thread.StartDisposal(); if (m_workerThreadID != Thread.CurrentThread.ManagedThreadId) InternalDisposeAllResources(); } // Completes the disposal of the class. [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")] private void InternalDisposeAllResources() { lock (m_disposeSync) { if ((object)m_waitForDispose != null) { m_waitForDispose.WaitOne(); m_waitForDispose.Dispose(); m_weakCallbackToken = null; m_waitForDispose = null; GC.SuppressFinalize(this); } } } // Executed by the worker thread private void OnRunningCallback(ThreadContainerBase.CallbackArgs args) { bool disposing = m_disposing; if (disposing && args.StartDisposalCallSuccessful) { args.ShouldDispose = true; TryCallback(ScheduledTaskRunningReason.Disposing); return; } TryCallback(ScheduledTaskRunningReason.Running); if (disposing) { args.ShouldDispose = true; TryCallback(ScheduledTaskRunningReason.Disposing); } } private void TryCallback(ScheduledTaskRunningReason args) { m_workerThreadID = Thread.CurrentThread.ManagedThreadId; try { if ((object)Running != null) Running(this, new EventArgs<ScheduledTaskRunningReason>(args)); } catch (Exception ex) { try { if ((object)UnhandledException != null) UnhandledException(this, new EventArgs<Exception>(ex)); } catch (Exception ex2) { Debug.WriteLine(ex2.ToString()); } } if (args == ScheduledTaskRunningReason.Disposing) { try { if ((object)Disposing != null) Disposing(this, EventArgs.Empty); } catch (Exception ex) { try { if ((object)UnhandledException != null) UnhandledException(this, new EventArgs<Exception>(ex)); } catch (Exception ex2) { Debug.WriteLine(ex2.ToString()); } } m_waitForDispose.Set(); InternalDisposeAllResources(); } m_workerThreadID = -1; } #endregion }}
//******************************************************************************************************
// ScheduledTask.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/08/2014 - Steven E. Chisholm
// Generated original version of source code.
//
//******************************************************************************************************
//------------------------------------------------------------------------------------------------------
// WARNING: This class contains very low-level logic and is optimized to have minimal locking. Before
// making any changes, be sure to consult the author as any bugs can introduce a race
// condition that will be very difficult to detect and fix. Additional desired functionality
// should likely result in another class being created rather than modifying this one.
//------------------------------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
namespace GSF.Threading
{
#region [ Enumerations ]
/// <summary>
/// Specifies the threading mode to use for the <see cref="ScheduledTask"/>
/// </summary>
public enum ThreadingMode
{
/// <summary>
/// A dedicated thread that is a foreground thread.
/// </summary>
DedicatedForeground,
/// <summary>
/// A dedicated thread that is a background thread.
/// </summary>
DedicatedBackground,
/// <summary>
/// A background thread from the thread pool.
/// </summary>
ThreadPool
}
/// <summary>
/// Metadata about why this worker was called.
/// </summary>
public enum ScheduledTaskRunningReason
{
/// <summary>
/// A normal run was scheduled.
/// </summary>
Running,
/// <summary>
/// Dispose was called and execution will terminate after this function call.
/// </summary>
Disposing,
}
#endregion
/// <summary>
/// Represents a way to schedule a task to be executed on a separate thread immediately or after a given time delay.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
public class ScheduledTask : IDisposable
{
#region [ Members ]
// Events
/// <summary>
/// Occurs every time the task should run.
/// </summary>
public event EventHandler<EventArgs<ScheduledTaskRunningReason>> Running;
/// <summary>
/// Occurs right before this task is disposed.
/// </summary>
public event EventHandler Disposing;
/// <summary>
/// Occurs when <see cref="Running"/> or <see cref="Disposing"/> event throws an exception.
/// </summary>
public event EventHandler<EventArgs<Exception>> UnhandledException;
// Fields
private int m_workerThreadID;
private readonly ThreadContainerBase m_thread; // This cannot be null as it would cause duplicate calls to Start to throw a null reference exception
private object m_weakCallbackToken; // A strong reference to the callback
private ManualResetEvent m_waitForDispose;
private readonly object m_disposeSync;
private volatile bool m_disposing;
#endregion
#region [ Constructors ]
/// <summary>
/// Creates a <see cref="ScheduledTask"/>.
/// </summary>
/// <param name="threadMode">The manner in which the scheduled task executes.</param>
/// <param name="priority">The thread priority to assign if a dedicated thread is used. This is ignored if using the thread-pool.</param>
public ScheduledTask(ThreadingMode threadMode = ThreadingMode.ThreadPool, ThreadPriority priority = ThreadPriority.Normal)
{
m_workerThreadID = -1;
m_waitForDispose = new ManualResetEvent(false);
m_disposeSync = new object();
switch (threadMode)
{
case ThreadingMode.DedicatedForeground:
m_thread = new ThreadContainerDedicated(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken), false, priority);
break;
case ThreadingMode.DedicatedBackground:
m_thread = new ThreadContainerDedicated(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken), true, priority);
break;
case ThreadingMode.ThreadPool:
m_thread = new ThreadContainerThreadpool(new WeakAction<ThreadContainerBase.CallbackArgs>(OnRunningCallback, out m_weakCallbackToken));
break;
default:
throw new ArgumentOutOfRangeException("threadMode");
}
}
/// <summary>
/// Cleans up the <see cref="ThreadContainerBase"/> thread since that class likely will never be garbage collected.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
~ScheduledTask()
{
//By starting the thread inside the finalizer, the ThreadContainer will exit because its weak reference will be set to null.
m_disposing = true;
Thread.MemoryBarrier();
m_thread.Start();
}
#endregion
#region [ Methods ]
/// <summary>
/// Starts the task immediately, or if one was scheduled, starts the scheduled task immediately
/// </summary>
/// <remarks>
/// <para>
/// If this is called after a <see cref="Start(int)"/> the timer will be canceled
/// and the process will still start immediately.
/// </para>
/// <para>
/// This method is safe to call from any thread, including the worker thread.
/// If disposed, this method will no nothing.
/// </para>
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start()
{
m_thread.Start();
}
/// <summary>
/// Starts a timer to run the task after a provided interval.
/// </summary>
/// <param name="delay">the delay in milliseconds before the task should run</param>
/// <remarks>
/// <para>
/// If a timer is currently pending, this function will do nothing. Do not use this
/// function to reset or restart an existing timer.
/// </para>
/// <para>
/// If called while working, a subsequent timer will be scheduled, but delay will not
/// start until after the worker has completed.
/// </para>
/// <para>
/// This method is safe to call from any thread, including the worker thread.
/// If disposed, this method will no nothing.
/// </para>
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start(int delay)
{
m_thread.Start(delay);
}
/// <summary>
/// Starts the disposing process of exiting the worker thread.
/// </summary>
/// <remarks>
/// <para>Callback will be invoked one more time. Duplicate calls are ignored.</para>
/// <para>
/// Unless called from the worker thread, this method will block until the dispose
/// has successfully completed.
/// </para>
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly"), SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
public void Dispose()
{
m_disposing = true;
Thread.MemoryBarrier();
m_thread.StartDisposal();
if (m_workerThreadID != Thread.CurrentThread.ManagedThreadId)
InternalDisposeAllResources();
}
// Completes the disposal of the class.
[SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
private void InternalDisposeAllResources()
{
lock (m_disposeSync)
{
if ((object)m_waitForDispose != null)
{
m_waitForDispose.WaitOne();
m_waitForDispose.Dispose();
m_weakCallbackToken = null;
m_waitForDispose = null;
GC.SuppressFinalize(this);
}
}
}
// Executed by the worker thread
private void OnRunningCallback(ThreadContainerBase.CallbackArgs args)
{
bool disposing = m_disposing;
if (disposing && args.StartDisposalCallSuccessful)
{
args.ShouldDispose = true;
TryCallback(ScheduledTaskRunningReason.Disposing);
return;
}
TryCallback(ScheduledTaskRunningReason.Running);
if (disposing)
{
args.ShouldDispose = true;
TryCallback(ScheduledTaskRunningReason.Disposing);
}
}
private void TryCallback(ScheduledTaskRunningReason args)
{
m_workerThreadID = Thread.CurrentThread.ManagedThreadId;
try
{
if ((object)Running != null)
Running(this, new EventArgs<ScheduledTaskRunningReason>(args));
}
catch (Exception ex)
{
try
{
if ((object)UnhandledException != null)
UnhandledException(this, new EventArgs<Exception>(ex));
}
catch (Exception ex2)
{
Debug.WriteLine(ex2.ToString());
}
}
if (args == ScheduledTaskRunningReason.Disposing)
{
try
{
if ((object)Disposing != null)
Disposing(this, EventArgs.Empty);
}
catch (Exception ex)
{
try
{
if ((object)UnhandledException != null)
UnhandledException(this, new EventArgs<Exception>(ex));
}
catch (Exception ex2)
{
Debug.WriteLine(ex2.ToString());
}
}
m_waitForDispose.Set();
InternalDisposeAllResources();
}
m_workerThreadID = -1;
}
#endregion
}
}