Skip to content

Instantly share code, notes, and snippets.

@long2know
Last active June 26, 2022 17:41
Show Gist options
  • Save long2know/48a623966b178e468271c6460966ff3e to your computer and use it in GitHub Desktop.
Save long2know/48a623966b178e468271c6460966ff3e to your computer and use it in GitHub Desktop.
Simple Task Scheduler
/// <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