//****************************************************************************************************** // 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))); } } }