//****************************************************************************************************** // 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 ] /// /// Specifies the threading mode to use for the /// public enum ThreadingMode { /// /// A dedicated thread that is a foreground thread. /// DedicatedForeground, /// /// A dedicated thread that is a background thread. /// DedicatedBackground, /// /// A background thread from the thread pool. /// ThreadPool } /// /// Metadata about why this worker was called. /// public enum ScheduledTaskRunningReason { /// /// A normal run was scheduled. /// Running, /// /// Dispose was called and execution will terminate after this function call. /// Disposing, } #endregion /// /// Represents a way to schedule a task to be executed on a separate thread immediately or after a given time delay. /// [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] public class ScheduledTask : IDisposable { #region [ Members ] // Events /// /// Occurs every time the task should run. /// public event EventHandler> Running; /// /// Occurs right before this task is disposed. /// public event EventHandler Disposing; /// /// Occurs when or event throws an exception. /// public event EventHandler> 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 ManualResetEvent m_waitForDispose; private readonly object m_disposeSync; private volatile bool m_disposing; #endregion #region [ Constructors ] /// /// Creates a . /// /// The manner in which the scheduled task executes. /// The thread priority to assign if a dedicated thread is used. This is ignored if using the thread-pool. /// Adds a handler to that requires this class to be disposed /// when the application is shutdown. Note: If this object has been garbage collected, this will have no effect. public ScheduledTask(ThreadingMode threadMode = ThreadingMode.ThreadPool, ThreadPriority priority = ThreadPriority.Normal, bool disposeOnShutdown = false) { m_workerThreadID = -1; m_waitForDispose = new ManualResetEvent(false); m_disposeSync = new object(); switch (threadMode) { case ThreadingMode.DedicatedForeground: m_thread = new ThreadContainerDedicated(OnRunningCallback, Dispose, false, priority, disposeOnShutdown); break; case ThreadingMode.DedicatedBackground: m_thread = new ThreadContainerDedicated(OnRunningCallback, Dispose, true, priority, disposeOnShutdown); break; case ThreadingMode.ThreadPool: m_thread = new ThreadContainerThreadpool(OnRunningCallback, Dispose, disposeOnShutdown); break; default: throw new ArgumentOutOfRangeException(nameof(threadMode)); } } /// /// Cleans up the thread since that class likely will never be garbage collected. /// [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 ] /// /// For foreground threads, a shutdown handler is registered to dispose of the Thread so it doesn't keep the process running. /// However, for the Logger, shutting down this thread will prevent shutdown messages from showing up in the logger. /// By calling this method, it declares that the coder will dispose of this class when it is finished and does not want the /// Shutdown handler to do it. /// internal void IgnoreShutdownEvent() { m_thread.IgnoreShutdownEvent(); } /// /// Starts the task immediately, or if one was scheduled, starts the scheduled task immediately /// /// /// /// If this is called after a the timer will be canceled /// and the process will still start immediately. /// /// /// This method is safe to call from any thread, including the worker thread. /// If disposed, this method will no nothing. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start() { m_thread.Start(); } /// /// Starts a timer to run the task after a provided interval. /// /// the delay in milliseconds before the task should run /// /// /// If a timer is currently pending, this function will do nothing. Do not use this /// function to reset or restart an existing timer. /// /// /// If called while working, a subsequent timer will be scheduled, but delay will not /// start until after the worker has completed. /// /// /// This method is safe to call from any thread, including the worker thread. /// If disposed, this method will no nothing. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(int delay) { m_thread.Start(delay); } /// /// Starts the disposing process of exiting the worker thread. /// /// /// Callback will be invoked one more time. Duplicate calls are ignored. /// /// Unless called from the worker thread, this method will block until the dispose /// has successfully completed. /// /// [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_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(args)); } catch (Exception ex) { try { if ((object)UnhandledException != null) UnhandledException(this, new EventArgs(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(ex)); } catch (Exception ex2) { Debug.WriteLine(ex2.ToString()); } } m_waitForDispose.Set(); InternalDisposeAllResources(); } m_workerThreadID = -1; } #endregion } }