diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index dcc20bd..f1b77a5 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -113,6 +113,9 @@ + + + diff --git a/ICD.Common.Utils/Services/Scheduler/ActionSchedulerService.cs b/ICD.Common.Utils/Services/Scheduler/ActionSchedulerService.cs new file mode 100644 index 0000000..ca601d6 --- /dev/null +++ b/ICD.Common.Utils/Services/Scheduler/ActionSchedulerService.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Utils.Extensions; +using ICD.Common.Utils.Services.Logging; +using ICD.Common.Utils.Timers; + +namespace ICD.Common.Utils.Services.Scheduler +{ + public sealed class ActionSchedulerService : IActionSchedulerService, IDisposable + { + private const long MAX_TIMER_INTERVAL = 5*60*1000; // 5 minutes + + private readonly List m_Actions; + private readonly SafeTimer m_Timer; + private readonly SafeCriticalSection m_CriticalSection; + + public ActionSchedulerService() + { + m_Actions = new List(); + m_Timer = new SafeTimer(TimerCallback, -1); + m_CriticalSection = new SafeCriticalSection(); + } + + public void Dispose() + { + foreach (IScheduledAction action in m_Actions) + { + Unsubscribe(action); + + IDisposable disposable = action as IDisposable; + if (disposable != null) + disposable.Dispose(); + } + + m_Actions.Clear(); + } + + #region Methods + + public void Add(IScheduledAction action) + { + m_CriticalSection.Enter(); + try + { + Subscribe(action); + m_Actions.AddSorted(action, a => a.NextRunTime); + } + finally + { + m_CriticalSection.Leave(); + } + } + + public void Remove(IScheduledAction action) + { + m_CriticalSection.Enter(); + try + { + Unsubscribe(action); + m_Actions.Remove(action); + } + finally + { + m_CriticalSection.Leave(); + } + } + + public override string ToString() + { + return "ActionSchedulerService"; + } + + #endregion + + #region Private Methods + + private void TimerCallback() + { + IScheduledAction[] actionsToRun; + + m_CriticalSection.Enter(); + try + { + actionsToRun = m_Actions + .Where(a => a.NextRunTime < DateTime.Now) + .OrderBy(a => a.NextRunTime) + .ToArray(); + } + finally + { + m_CriticalSection.Leave(); + } + + // leave the critical section so any actions that run can enter + foreach (IScheduledAction action in actionsToRun) + { + try + { + action.Run(); + } + catch (Exception ex) + { + Log(eSeverity.Error, ex, "Error occurred while running scheduled action"); + } + } + + // enter again to check the closest next run time + m_CriticalSection.Enter(); + try + { + var action = m_Actions.FirstOrDefault(); + if (action == null) + return; + + m_Timer.Reset((long)(DateTime.Now - action.NextRunTime).TotalMilliseconds); + } + finally + { + m_CriticalSection.Leave(); + } + } + + private void Log(eSeverity severity, string message, params object[] args) + { + ILoggerService logger = ServiceProvider.TryGetService(); + if (logger == null) + return; + + logger.AddEntry(severity, string.Format("{0} - {1}", this, message), args); + } + + private void Log(eSeverity severity, Exception ex, string message, params object[] args) + { + ILoggerService logger = ServiceProvider.TryGetService(); + if (logger == null) + return; + + logger.AddEntry(severity, ex, string.Format("{0} - {1}", this, message), args); + } + + #endregion + + #region Action Callbacks + + private void Subscribe(IScheduledAction action) + { + action.OnScheduledRunTimeChanged += ActionOnScheduledRunTimeChanged; + } + + private void Unsubscribe(IScheduledAction action) + { + action.OnScheduledRunTimeChanged -= ActionOnScheduledRunTimeChanged; + } + + private void ActionOnScheduledRunTimeChanged(object sender, EventArgs eventArgs) + { + IScheduledAction action = sender as IScheduledAction; + if (action == null) + return; + + m_CriticalSection.Enter(); + try + { + m_Actions.Remove(action); + m_Actions.AddSorted(action, a => a.NextRunTime); + } + finally + { + m_CriticalSection.Leave(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/Services/Scheduler/IActionSchedulerService.cs b/ICD.Common.Utils/Services/Scheduler/IActionSchedulerService.cs new file mode 100644 index 0000000..2e0bf1a --- /dev/null +++ b/ICD.Common.Utils/Services/Scheduler/IActionSchedulerService.cs @@ -0,0 +1,9 @@ +namespace ICD.Common.Utils.Services.Scheduler +{ + public interface IActionSchedulerService + { + void Add(IScheduledAction action); + + void Remove(IScheduledAction action); + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/Services/Scheduler/IScheduledAction.cs b/ICD.Common.Utils/Services/Scheduler/IScheduledAction.cs new file mode 100644 index 0000000..de0f246 --- /dev/null +++ b/ICD.Common.Utils/Services/Scheduler/IScheduledAction.cs @@ -0,0 +1,19 @@ +using System; + +namespace ICD.Common.Utils.Services.Scheduler +{ + public interface IScheduledAction + { + /// + /// Raised when the scheduled run time has changed. The sender of the event must be the action itself + /// + event EventHandler OnScheduledRunTimeChanged; + + /// + /// Gets the next time this action should be run + /// + DateTime NextRunTime { get; } + + void Run(); + } +} \ No newline at end of file