//****************************************************************************************************** // SharedTimer.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: // ---------------------------------------------------------------------------------------------------- // 11/10/2016 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.ComponentModel; using GSF.Diagnostics; namespace GSF.Threading { /// /// Represents a timer class that will group registered timer event callbacks that operate on the same /// interval in order to optimize thread pool queuing. /// /// /// /// Externally the operations similar to the . /// Internally the timer pools callbacks with the same into a single timer where /// each callback is executed on the same thread, per instance of the . /// /// /// Any long running callbacks that have a risk of long delays should not use /// as this will effect the reliability of all of the other instances for a /// given . /// /// public sealed class SharedTimer : IDisposable { #region [ Members ] // Events /// /// Occurs when the timer interval elapses. /// public event EventHandler> Elapsed; /// /// Occurs when event throws an exception. /// public event EventHandler> UnhandledExceptions; // Fields private int m_interval; private bool m_enabled; private bool m_autoReset; private bool m_disposed; private readonly Action m_callback; private readonly SharedTimerScheduler m_scheduler; private WeakAction m_registeredCallback; // Since it is not expected that many shared timers will exist, log publisher is a member instance. // This will provide the initialization stack so it will be easier to distinguish this instance // of StaticTimer from other instances. private readonly LogPublisher m_log; #endregion #region [ Constructors ] /// /// Initializes a new instance of the . /// /// The scheduler to use. /// The interval of the timer, default is 100. [EditorBrowsable(EditorBrowsableState.Never)] internal SharedTimer(SharedTimerScheduler scheduler, int interval = 100) { if ((object)scheduler == null) throw new ArgumentNullException(nameof(scheduler)); if (scheduler.IsDisposed) throw new ArgumentException("Scheduler has been disposed", nameof(scheduler)); if (interval <= 0) throw new ArgumentOutOfRangeException(nameof(interval)); m_log = Logger.CreatePublisher(typeof(SharedTimerScheduler), MessageClass.Component); m_scheduler = scheduler; m_interval = interval; m_enabled = false; m_autoReset = true; m_callback = TimerCallback; m_scheduler = scheduler; } #endregion #region [ Properties ] /// /// Gets or sets flag that indicates whether the should raise the event only /// once false or repeatedly true. /// /// /// true the should raise the event each time the interval elapses; otherwise, /// false if it should raise the event only once, after the first time the interval elapses. /// The default is true. /// public bool AutoReset { get { return m_autoReset; } set { if (m_autoReset == value) return; m_autoReset = value; if (value && m_enabled) { m_registeredCallback?.Clear(); m_registeredCallback = m_scheduler.RegisterCallback(m_interval, m_callback); } } } /// /// Gets or sets flag that indicates whether the should raise the event. /// /// /// true if the should raise the event; otherwise, false. /// The default is false. /// public bool Enabled { get { return m_enabled; } set { if (m_disposed) throw new ObjectDisposedException(GetType().FullName); if (m_enabled == value) return; m_enabled = value; if (!m_enabled) m_registeredCallback?.Clear(); else m_registeredCallback = m_scheduler.RegisterCallback(m_interval, m_callback); } } /// /// Gets or sets the interval at which to raise the event. /// /// The time, in milliseconds, between events. /// /// The value must be greater than zero, and less than or equal to . /// The default is 100 milliseconds. /// public int Interval { get { return m_interval; } set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value)); if (value == m_interval) return; m_interval = value; if (m_enabled) { m_registeredCallback?.Clear(); m_registeredCallback = m_scheduler.RegisterCallback(m_interval, m_callback); } } } #endregion #region [ Methods ] /// /// Stops the timer. /// public void Close() { Enabled = false; } /// /// Stops the timer and prevents reuse of the class. /// public void Dispose() { Close(); m_disposed = true; } /// /// Starts raising the event by setting to true. /// public void Start() { Enabled = true; } /// /// Stops raising the event by setting to false. /// public void Stop() { Enabled = false; } /// /// Callback from . /// /// The time that the callback was signaled. private void TimerCallback(DateTime state) { if (!m_enabled) return; if (!m_autoReset) { m_enabled = false; m_registeredCallback?.Clear(); } try { Elapsed?.Invoke(this, new EventArgs(state)); } catch (Exception ex) { try { EventHandler> unhandledExceptions = UnhandledExceptions; if ((object)unhandledExceptions == null) m_log.Publish(MessageLevel.Info, "Swallowed exception", null, null, ex); else unhandledExceptions(this, new EventArgs(ex)); } catch (Exception ex2) { m_log.Publish(MessageLevel.Warning, MessageFlags.BugReport, "Unhandled Exception callback threw an error", ex2.ToString(), null, ex2); } } } #endregion } }