using System; #if SIMPLSHARP using Crestron.SimplSharp; #else using System.Threading; #endif using ICD.Common.Services; using ICD.Common.Services.Logging; namespace ICD.Common.Utils.Timers { /// /// SafeTimer wraps CTimer to hide some of the jank. /// public sealed class SafeTimer : IStateDisposable { #if SIMPLSHARP private readonly CTimer m_Timer; #else private readonly Timer m_Timer; private int m_DueTime, m_RepeatPeriod; #endif private readonly Action m_Callback; /// /// Returns true if this instance has been disposed. /// public bool IsDisposed { get; private set; } #region Constructors /// /// Creates a timer that is called every repeatPeriod in milliseconds. /// /// /// public SafeTimer(Action callback, long repeatPeriod) : this(callback, 0, repeatPeriod) { } /// /// Creates a timer that is called in dueTime milliseconds and then every /// repeatPeriod milliseconds afterwards. /// /// /// /// public SafeTimer(Action callback, long dueTime, long repeatPeriod) { m_Callback = callback; #if SIMPLSHARP m_Timer = new CTimer(SafeCallback, null, dueTime, repeatPeriod); #else m_DueTime = (int)dueTime; m_RepeatPeriod = (int)repeatPeriod; m_Timer = new Timer(SafeCallback, null, m_DueTime, m_RepeatPeriod); #endif } /// /// Creates a timer that is initially stopped. /// /// /// public static SafeTimer Stopped(Action callback) { SafeTimer output = new SafeTimer(callback, 0); output.Stop(); return output; } #endregion #region Methods /// /// Release resources. /// public void Dispose() { Stop(); m_Timer.Dispose(); IsDisposed = true; } /// /// Stops the timer. /// public void Stop() { #if SIMPLSHARP m_Timer.Stop(); #else m_Timer.Change(Timeout.Infinite, Timeout.Infinite); #endif } /// /// Immediately calls the callback and resets the timer /// public void Trigger() { #if SIMPLSHARP m_Timer.Reset(); #else m_Timer.Change(0, m_RepeatPeriod); #endif } /// /// Callback is called after the dueTime milliseconds. /// /// public void Reset(long dueTime) { #if SIMPLSHARP m_Timer.Reset(dueTime); #else m_DueTime = (int)dueTime; m_Timer.Change(m_DueTime, m_RepeatPeriod); #endif } /// /// Callback is called after the dueTime milliseconds and every repeatPeriod milliseconds. /// /// /// public void Reset(long dueTime, long repeatPeriod) { #if SIMPLSHARP m_Timer.Reset(dueTime, repeatPeriod); #else m_DueTime = (int)dueTime; m_RepeatPeriod = (int)repeatPeriod; m_Timer.Change(m_DueTime, m_RepeatPeriod); #endif } #endregion #region Private Methods /// /// Only executes the callback if the timer has not been disposed. /// Catches any exceptions and logs them. /// /// private void SafeCallback(object unused) { // Essentially the meat of this class. There's some weirdness with the garbage collector where // the reference to the timer will be cleared, and eventually the CTimer will call the callback // despite being stopped/disposed. if (m_Timer == null #if SIMPLSHARP || m_Timer.Disposed #endif ) return; try { m_Callback(); } catch (Exception e) { LogException(e); } } private void LogException(Exception e) { string message = string.Format("{0} failed to execute callback - {1}", GetType().Name, e.Message); ServiceProvider.TryGetService().AddEntry(eSeverity.Error, e, message); } #endregion } }