Merge branch 'feat/action-scheduler' of Common/Utils into dev

This commit is contained in:
Chris Cameron
2018-07-12 20:18:48 +00:00
committed by Gogs
7 changed files with 336 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Linq;
namespace ICD.Common.Utils.Extensions
{
@@ -28,5 +29,39 @@ namespace ICD.Common.Utils.Extensions
// Todo - Better handle different cultures
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;
}
}
}

View 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;
}
}
}

View File

@@ -113,6 +113,10 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RecursionUtils.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\LogItem.cs" />
<Compile Include="Services\Logging\LogItemEventArgs.cs" />

View File

@@ -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();
}
}

View 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
}
}

View File

@@ -0,0 +1,9 @@
namespace ICD.Common.Utils.Services.Scheduler
{
public interface IActionSchedulerService
{
void Add(IScheduledAction action);
void Remove(IScheduledAction action);
}
}

View 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();
}
}