//******************************************************************************************************
// EventTimer.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://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:
// ----------------------------------------------------------------------------------------------------
// 11/17/2016 - Steven E. Chisholm
// Generated original version of source code.
//
//
//******************************************************************************************************
using System;
using GSF.Diagnostics;
namespace GSF.Threading
{
///
/// A reoccurring timer that fires on a given interval. This event timer will always fire at the top of the specified interval.
/// If the callback takes too long, the next interval will be skipped.
///
public class EventTimer
: DisposableLoggingClassBase
{
///
/// An event that fires every time the specified period elapses.
///
public event EventHandler Elapsed;
ScheduledTask m_timer;
TimeSpan m_period;
TimeSpan m_dayOffset;
bool m_started;
bool m_disposed;
///
/// Creates a
///
///
///
EventTimer(TimeSpan period, TimeSpan dayOffset)
: base(MessageClass.Component)
{
m_period = period;
m_dayOffset = dayOffset;
Log.InitialStackMessages = Log.InitialStackMessages.Union("Timer", string.Format("EventTimer: {0} in {1}", m_period.ToString(), m_dayOffset.ToString()));
}
///
/// The amount of time before the next interval will occur.
///
public TimeSpan TimeUntilNextExecution
{
get
{
long current = DateTime.UtcNow.Ticks;
long subtractOffset = current - m_dayOffset.Ticks;
long remainderTicks = m_period.Ticks - subtractOffset % m_period.Ticks;
int delay = (int)(remainderTicks / TimeSpan.TicksPerMillisecond) + 1;
if (delay < 10)
delay += (int)m_period.TotalMilliseconds;
return new TimeSpan(delay * TimeSpan.TicksPerMillisecond);
}
}
///
/// Modifies the timer. Note, it takes some time for this timer to go into effect.
///
///
///
public void ChangeTimer(TimeSpan period, TimeSpan dayOffset = default(TimeSpan))
{
m_period = period;
m_dayOffset = dayOffset;
}
///
/// This timer will reliably fire the directory polling every interval.
///
///
///
void TimerRunning(object sender, EventArgs e)
{
//This cannot be combined with m_directoryPolling because
//Scheduled task does not support managing multiple conflicting timers.
if (e.Argument == ScheduledTaskRunningReason.Disposing)
return;
try
{
Elapsed?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
Log.Publish(MessageLevel.Error, "Event Timer Exception on raising event.", null, null, ex);
}
RestartTimer();
}
///
/// Immediately executes the timer, not waiting for the elapsed interval.
///
public void RunNow()
{
m_timer.Start();
}
void RestartTimer()
{
long current = DateTime.UtcNow.Ticks;
long subtractOffset = current - m_dayOffset.Ticks;
long remainderTicks = m_period.Ticks - subtractOffset % m_period.Ticks;
int delay = (int)(remainderTicks / TimeSpan.TicksPerMillisecond) + 1;
if (delay < 10)
delay += (int)m_period.TotalMilliseconds;
m_timer.Start(delay);
}
///
/// Starts the watching
///
public void Start(ThreadingMode mode = ThreadingMode.ThreadPool)
{
if (m_started)
return;
m_started = true;
Log.Publish(MessageLevel.Debug, "EventTimer Started");
m_timer = new ScheduledTask(mode);
m_timer.Running += TimerRunning;
RestartTimer();
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_timer")]
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
m_disposed = true;
m_timer?.Dispose();
m_timer = null;
}
base.Dispose(disposing);
}
///
/// Creates a on the specified interval.
///
/// How often the time will reset
/// The offset as represented in UTC time when the event should fire
///
public static EventTimer Create(TimeSpan period, TimeSpan dayOffset = default(TimeSpan))
{
return new EventTimer(period, dayOffset);
}
///
/// Creates a on the specified interval.
///
/// How often the time will reset
/// The offset as represented in UTC time when the event should fire
///
public static EventTimer CreateSeconds(double periodInSecond, double dayOffsetInSecond = 0)
{
return new EventTimer(new TimeSpan((long)(periodInSecond * TimeSpan.TicksPerSecond)), new TimeSpan((long)(dayOffsetInSecond * TimeSpan.TicksPerSecond)));
}
///
/// Creates a on the specified interval.
///
/// How often the time will reset
/// The offset as represented in UTC time when the event should fire
///
public static EventTimer CreateMinutes(double periodInMinutes, double dayOffsetInMinutes = 0)
{
return new EventTimer(new TimeSpan((long)(periodInMinutes * TimeSpan.TicksPerMinute)), new TimeSpan((long)(dayOffsetInMinutes * TimeSpan.TicksPerMinute)));
}
///
/// Creates a on the specified interval.
///
/// How often the time will reset
/// The offset as represented in UTC time when the event should fire
///
public static EventTimer CreateHours(double periodInHours, double dayOffsetInHours = 0)
{
return new EventTimer(new TimeSpan((long)(periodInHours * TimeSpan.TicksPerHour)), new TimeSpan((long)(dayOffsetInHours * TimeSpan.TicksPerHour)));
}
}
}