//******************************************************************************************************
// 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 (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 => m_autoReset;
set
{
if (m_autoReset == value)
return;
m_autoReset = value;
if (!m_autoReset || !m_enabled)
return;
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 => m_enabled;
set
{
if (m_enabled == value)
return;
// Check dispose after checking if there was an actual state change (in case Stop or Close is called after Dispose)
if (m_disposed)
throw new ObjectDisposedException(GetType().FullName);
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 => m_interval;
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
if (value == m_interval)
return;
m_interval = value;
if (!m_enabled)
return;
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
}
}