Last active
June 26, 2022 17:41
-
-
Save long2know/48a623966b178e468271c6460966ff3e to your computer and use it in GitHub Desktop.
Simple Task Scheduler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Simple repeater based on a timed interval | |
/// </summary> | |
public static class TaskRepeater | |
{ | |
public static Task Interval(TimeSpan pollInterval, Action action, CancellationToken token, bool runImmediately = false) | |
{ | |
// We don't use Observable.Interval: | |
// If we block, the values start bunching up behind each other. | |
return Task.Factory.StartNew( | |
() => | |
{ | |
if (runImmediately) | |
{ | |
for (;;) | |
{ | |
action(); | |
if (token.WaitCancellationRequested(pollInterval)) | |
break; | |
} | |
} | |
else | |
{ | |
for (;;) | |
{ | |
if (token.WaitCancellationRequested(pollInterval)) | |
break; | |
action(); | |
} | |
} | |
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); | |
} | |
} | |
/// <summary> | |
/// Task scheduler with many options and FluentAPI | |
/// </summary> | |
public class TaskSchedule | |
{ | |
private int _repeatMilliseconds = 0; | |
private bool _repeatDaily = false; | |
private bool _repeatMonthly = false; | |
private string _name = "NoName"; | |
private List<DayOfWeek> _daysOfWeek = new List<DayOfWeek>() { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, | |
DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday }; | |
private DateTime _lastRun = DateTime.UtcNow.AddDays(-1); | |
private DateTime _nextRun = DateTime.UtcNow.AddDays(-1); | |
private DateTime _startDate = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1); | |
private TimeSpan _startTime = new TimeSpan(0, 0, 0); | |
private bool _acceleratedTime = false; | |
private Action _action; | |
private CancellationToken _token; | |
public DateTime NextRun { get { return _nextRun; } set { _nextRun = value; } } | |
public DateTime LastRun { get { return _lastRun; } set { _lastRun = value; } } | |
public int RepeatMilliseconds { get { return _repeatMilliseconds; } set { _repeatMilliseconds = value; } } | |
public bool RepeatDaily { get { return _repeatDaily; } set { _repeatDaily = value; _lastRun = _startDate + _startTime; } } | |
public bool RepeatMonthly { get { return _repeatMonthly; } set { _repeatMonthly = value; _lastRun = _startDate + _startTime; } } | |
public string Name { get { return _name; } set { _name = value; } } | |
public List<DayOfWeek> DaysOfWeek { get { return _daysOfWeek; } set { _daysOfWeek = value; } } | |
public DateTime StartDate { get { return _startDate; } set { _startDate = value; _lastRun = _startDate + _startTime; } } | |
public TimeSpan StartTime { get { return _startTime; } set { _startTime = value; _lastRun = _startDate + _startTime; } } | |
public bool AcceleratedTime { get { return _acceleratedTime; } set { _acceleratedTime = value; } } | |
public Action ScheduleAction { get { return _action; } set { _action = value; } } | |
public CancellationToken ScheduleToken { get { return _token; } set { _token = value; } } | |
public TaskSchedule() | |
{ | |
} | |
public DateTime GetNextRun(DateTime? dateTime = null) | |
{ | |
var currentDateTime = dateTime ?? TaskScheduleTimer.UtcNow; | |
var testDate = currentDateTime.Date; | |
if (_nextRun == DateTime.MinValue) | |
{ | |
_lastRun = _nextRun = testDate = currentDateTime.AddMilliseconds(1); | |
return testDate; | |
} | |
if (_repeatDaily) | |
{ | |
testDate = testDate + _startTime; | |
if (testDate < currentDateTime) | |
{ | |
do | |
{ | |
testDate = testDate.AddDays(1); | |
} while (!_daysOfWeek.Any(x => x == testDate.DayOfWeek) && _daysOfWeek != null && _daysOfWeek.Count > 0); | |
} | |
} | |
else if (_repeatMonthly) | |
{ | |
testDate = new DateTime(testDate.Year, testDate.Month, _startDate.Date.Day) + _startTime; | |
if (testDate < currentDateTime) | |
{ | |
testDate = testDate.AddMonths(1); | |
testDate = new DateTime(testDate.Year, testDate.Month, _startDate.Date.Day) + _startTime; | |
while (!_daysOfWeek.Any(x => x == testDate.DayOfWeek) && _daysOfWeek != null && _daysOfWeek.Count > 0) | |
{ | |
testDate = testDate.AddDays(1); | |
} | |
} | |
} | |
else | |
{ | |
_lastRun = _nextRun; | |
testDate = _lastRun; | |
do | |
{ | |
testDate = testDate.AddMilliseconds(_repeatMilliseconds); | |
} while (testDate < currentDateTime); | |
} | |
_nextRun = testDate; | |
return testDate; | |
} | |
public Task CreateTask() | |
{ | |
return Task.Factory.StartNew(() => | |
{ | |
TimeSpan pollInterval; | |
DateTime nextRunDate; | |
for (;;) | |
{ | |
var currentDateTime = TaskScheduleTimer.UtcNow; | |
nextRunDate = this.GetNextRun(currentDateTime); | |
pollInterval = nextRunDate - currentDateTime; | |
if (_acceleratedTime) | |
{ | |
pollInterval = TimeSpan.FromMilliseconds(500); | |
} | |
Console.WriteLine(string.Format("[* {0} *]: Sleeping until {1:MM/dd/yyyy HH:mm:ss.fff}, Interval: {2}", _name, nextRunDate.ToLocalTime(), pollInterval)); | |
// We have to chunk the wait if we exceed Int32.MaxValue | |
var totalMilliseconds = pollInterval.TotalMilliseconds; | |
if (totalMilliseconds <= int.MaxValue) | |
{ | |
if (_token.WaitCancellationRequested(pollInterval)) | |
break; | |
} | |
else | |
{ | |
while (totalMilliseconds > 0 && !_token.IsCancellationRequested) | |
{ | |
var currentDelay = totalMilliseconds > int.MaxValue ? int.MaxValue : (int)totalMilliseconds; | |
if (_token.WaitCancellationRequested(TimeSpan.FromMilliseconds(currentDelay))) | |
break; | |
totalMilliseconds -= currentDelay; | |
} | |
if (_token.IsCancellationRequested) | |
{ | |
break; | |
} | |
} | |
_action(); | |
if (_acceleratedTime) | |
{ | |
TaskScheduleTimer.SetCurrent(nextRunDate); | |
} | |
} | |
if (_token.IsCancellationRequested) | |
{ | |
Console.WriteLine(string.Format("[* {0} *]: Cancelled", _name)); | |
} | |
}, _token, TaskCreationOptions.LongRunning, TaskScheduler.Default); | |
} | |
} | |
public class TaskScheduleTimer | |
{ | |
private static DateTime _startTime; | |
private static Stopwatch _stopWatch = null; | |
private static TimeSpan _maxIdle = TimeSpan.FromSeconds(10); | |
public static DateTime UtcNow | |
{ | |
get | |
{ | |
if ((_stopWatch == null) || (_startTime.Add(_maxIdle) < DateTime.UtcNow)) | |
{ | |
Reset(); | |
} | |
return _startTime.AddTicks(_stopWatch.Elapsed.Ticks); | |
} | |
} | |
public static void SetCurrent(DateTime dateTime) | |
{ | |
_startTime = dateTime; | |
} | |
private static void Reset() | |
{ | |
_startTime = DateTime.UtcNow; | |
_stopWatch = Stopwatch.StartNew(); | |
} | |
public static Stopwatch Stopwatch | |
{ | |
get { return _stopWatch; } | |
} | |
} | |
public static class Extensions | |
{ | |
public static TaskSchedule WithName(this TaskSchedule schedule, string name) | |
{ | |
schedule.Name = name; | |
return schedule; | |
} | |
public static TaskSchedule RepeatMilliseconds(this TaskSchedule schedule, int milliseconds) | |
{ | |
schedule.RepeatMilliseconds = milliseconds; | |
return schedule; | |
} | |
public static TaskSchedule RepeatSeconds(this TaskSchedule schedule, int seconds) | |
{ | |
schedule.RepeatMilliseconds = seconds * 1000; | |
return schedule; | |
} | |
public static TaskSchedule RepeatMinutes(this TaskSchedule schedule, int minutes) | |
{ | |
schedule.RepeatMilliseconds = minutes * 60000; | |
return schedule; | |
} | |
public static TaskSchedule WithRunImmediately(this TaskSchedule schedule) | |
{ | |
schedule.NextRun = DateTime.MinValue; | |
return schedule; | |
} | |
public static TaskSchedule RepeatDaily(this TaskSchedule schedule, bool repeatDaily = true) | |
{ | |
schedule.RepeatDaily = repeatDaily; | |
schedule.RepeatMonthly = false; | |
return schedule; | |
} | |
public static TaskSchedule RepeatMonthly(this TaskSchedule schedule, bool repeatMonthly = true) | |
{ | |
schedule.RepeatMonthly = repeatMonthly; | |
schedule.RepeatDaily = false; | |
return schedule; | |
} | |
public static TaskSchedule WithDaysOfWeek(this TaskSchedule schedule, List<DayOfWeek> daysOfWeek) | |
{ | |
schedule.DaysOfWeek = daysOfWeek; | |
return schedule; | |
} | |
public static TaskSchedule WithStartDate(this TaskSchedule schedule, DateTime startDate) | |
{ | |
schedule.StartDate = startDate.Date >= DateTime.UtcNow.Date ? startDate : DateTime.UtcNow.Date; | |
return schedule; | |
} | |
public static TaskSchedule WithStartTime(this TaskSchedule schedule, TimeSpan startTime) | |
{ | |
schedule.StartTime = startTime; | |
if (schedule.RepeatMilliseconds > 0) { schedule.NextRun = schedule.StartDate + schedule.StartTime; }; | |
return schedule; | |
} | |
public static TaskSchedule WithAcceleratedTime(this TaskSchedule schedule) | |
{ | |
schedule.AcceleratedTime = true; | |
return schedule; | |
} | |
public static TaskSchedule WithAction(this TaskSchedule schedule, Action action, CancellationToken token) | |
{ | |
schedule.ScheduleAction = action; | |
schedule.ScheduleToken = token; | |
schedule.CreateTask(); | |
return schedule; | |
} | |
public static bool WaitCancellationRequested(this CancellationToken token, TimeSpan timeout) | |
{ | |
return token.WaitHandle.WaitOne(timeout); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment