mirror of
https://github.com/ICDSystems/ICD.Common.Utils.git
synced 2026-02-16 13:15:07 +00:00
Merge branch 'feat/action-scheduler' of Common/Utils into dev
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace ICD.Common.Utils.Extensions
|
namespace ICD.Common.Utils.Extensions
|
||||||
{
|
{
|
||||||
@@ -28,5 +29,39 @@ namespace ICD.Common.Utils.Extensions
|
|||||||
// Todo - Better handle different cultures
|
// Todo - Better handle different cultures
|
||||||
return extends.ToString("HH:mm:ss:fff");
|
return extends.ToString("HH:mm:ss:fff");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the closest DateTime to the target time that is greater than the target time
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="a"></param>
|
||||||
|
/// <param name="b"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static DateTime? NextEarliestTime(this DateTime target, params DateTime[] times)
|
||||||
|
{
|
||||||
|
if (times.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DateTime earliestTime;
|
||||||
|
bool success = times.OrderBy(dt => dt).TryFirst(dt => target < dt, out earliestTime);
|
||||||
|
return success ? earliestTime : (DateTime?) null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the closest DateTime to the target time that is less than the target time
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="a"></param>
|
||||||
|
/// <param name="b"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static DateTime? PreviousLatestTime(this DateTime target, params DateTime[] times)
|
||||||
|
{
|
||||||
|
if (times.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DateTime latestTime;
|
||||||
|
bool success = times.OrderByDescending(dt => dt).TryFirst(dt => target > dt, out latestTime);
|
||||||
|
return success ? latestTime : (DateTime?) null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
ICD.Common.Utils/Extensions/DayOfWeekExtensions.cs
Normal file
17
ICD.Common.Utils/Extensions/DayOfWeekExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICD.Common.Utils.Extensions
|
||||||
|
{
|
||||||
|
public static class DayOfWeekExtensions
|
||||||
|
{
|
||||||
|
public static bool IsWeekday(this DayOfWeek day)
|
||||||
|
{
|
||||||
|
return !IsWeekend(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWeekend(this DayOfWeek day)
|
||||||
|
{
|
||||||
|
return day == DayOfWeek.Saturday || day == DayOfWeek.Sunday;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,6 +113,10 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="RecursionUtils.cs" />
|
<Compile Include="RecursionUtils.cs" />
|
||||||
<Compile Include="ReprBuilder.cs" />
|
<Compile Include="ReprBuilder.cs" />
|
||||||
|
<Compile Include="Services\Scheduler\AbstactScheduledAction.cs" />
|
||||||
|
<Compile Include="Services\Scheduler\ActionSchedulerService.cs" />
|
||||||
|
<Compile Include="Services\Scheduler\IActionSchedulerService.cs" />
|
||||||
|
<Compile Include="Services\Scheduler\IScheduledAction.cs" />
|
||||||
<Compile Include="Services\Logging\ILoggerService.cs" />
|
<Compile Include="Services\Logging\ILoggerService.cs" />
|
||||||
<Compile Include="Services\Logging\LogItem.cs" />
|
<Compile Include="Services\Logging\LogItem.cs" />
|
||||||
<Compile Include="Services\Logging\LogItemEventArgs.cs" />
|
<Compile Include="Services\Logging\LogItemEventArgs.cs" />
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using ICD.Common.Utils.EventArguments;
|
||||||
|
using ICD.Common.Utils.Extensions;
|
||||||
|
|
||||||
|
namespace ICD.Common.Utils.Services.Scheduler
|
||||||
|
{
|
||||||
|
public abstract class AbstractScheduledAction : IScheduledAction
|
||||||
|
{
|
||||||
|
public event EventHandler OnScheduledRunTimeChanged;
|
||||||
|
|
||||||
|
private DateTime? m_NextRunTime;
|
||||||
|
|
||||||
|
public DateTime? NextRunTime
|
||||||
|
{
|
||||||
|
get { return m_NextRunTime; }
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (m_NextRunTime == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_NextRunTime = value;
|
||||||
|
|
||||||
|
OnScheduledRunTimeChanged.Raise(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
RunFinal();
|
||||||
|
NextRunTime = GetNextRunTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateNextRunTime()
|
||||||
|
{
|
||||||
|
NextRunTime = GetNextRunTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs when the action has hit its scheduled time
|
||||||
|
/// </summary>
|
||||||
|
public abstract void RunFinal();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs after RunFinal in order to set the next run time of this action
|
||||||
|
/// </summary>
|
||||||
|
public abstract DateTime? GetNextRunTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
203
ICD.Common.Utils/Services/Scheduler/ActionSchedulerService.cs
Normal file
203
ICD.Common.Utils/Services/Scheduler/ActionSchedulerService.cs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
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 readonly List<IScheduledAction> m_Actions;
|
||||||
|
private readonly SafeTimer m_Timer;
|
||||||
|
private readonly SafeCriticalSection m_CriticalSection;
|
||||||
|
|
||||||
|
public ActionSchedulerService()
|
||||||
|
{
|
||||||
|
m_Actions = new List<IScheduledAction>();
|
||||||
|
m_Timer = new SafeTimer(TimerCallback, -1);
|
||||||
|
m_CriticalSection = new SafeCriticalSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
m_Timer.Stop();
|
||||||
|
m_Timer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public void Add(IScheduledAction action)
|
||||||
|
{
|
||||||
|
m_CriticalSection.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Subscribe(action);
|
||||||
|
m_Actions.AddSorted(action, a => a.NextRunTime);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
RescheduleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(IScheduledAction action)
|
||||||
|
{
|
||||||
|
m_CriticalSection.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Unsubscribe(action);
|
||||||
|
m_Actions.Remove(action);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
RescheduleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_CriticalSection.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (IScheduledAction action in m_Actions)
|
||||||
|
{
|
||||||
|
Unsubscribe(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Actions.Clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
RescheduleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RescheduleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RescheduleTimer()
|
||||||
|
{
|
||||||
|
// enter again to check the closest next run time
|
||||||
|
m_CriticalSection.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var action = m_Actions.FirstOrDefault(a => a.NextRunTime != null);
|
||||||
|
if (action == null || action.NextRunTime == null)
|
||||||
|
{
|
||||||
|
m_Timer.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long msToNextAction = (long)(action.NextRunTime.Value - DateTime.Now).TotalMilliseconds;
|
||||||
|
m_Timer.Reset(msToNextAction);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(eSeverity severity, string message, params object[] args)
|
||||||
|
{
|
||||||
|
ILoggerService logger = ServiceProvider.TryGetService<ILoggerService>();
|
||||||
|
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<ILoggerService>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
RescheduleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace ICD.Common.Utils.Services.Scheduler
|
||||||
|
{
|
||||||
|
public interface IActionSchedulerService
|
||||||
|
{
|
||||||
|
void Add(IScheduledAction action);
|
||||||
|
|
||||||
|
void Remove(IScheduledAction action);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
ICD.Common.Utils/Services/Scheduler/IScheduledAction.cs
Normal file
20
ICD.Common.Utils/Services/Scheduler/IScheduledAction.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using ICD.Common.Utils.EventArguments;
|
||||||
|
|
||||||
|
namespace ICD.Common.Utils.Services.Scheduler
|
||||||
|
{
|
||||||
|
public interface IScheduledAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the scheduled run time has changed. The sender of the event must be the action itself
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler OnScheduledRunTimeChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next time this action should be run
|
||||||
|
/// </summary>
|
||||||
|
DateTime? NextRunTime { get; }
|
||||||
|
|
||||||
|
void Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user