Scenario:
I wanted a task scheduler/doer that enables my application to schedule some tasks to be executed in a specified time but in the same order they were scheduled in or depending on their priorities (DataFlow
like). I also wanted it to enable the usage of C# 5 Async/Await functionality so I looked up over the Internet and didn't found something the truly fit my needs so I decided to implement one... a simple yet advanced (in my opinion) task scheduler that exactly achieved what I want. However, I want to improve it by hearing your opinions about it.
Implementation:
1) Scheduler
Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ATScheduler
{
public abstract class Scheduler
{
#region Fields
private readonly TimeSpan _baseInterval;
private readonly TimeSpan _executionTimeOut;
private readonly List<IJob> _jobs;
private readonly object _sync;
private readonly Timer _timer;
private bool _paused;
#endregion
#region Constructors
protected Scheduler()
{
_jobs = new List<IJob>();
_sync = new object();
_baseInterval = TimeSpan.FromMilliseconds(12000);
_executionTimeOut = TimeSpan.FromMilliseconds(30000);
_timer = new Timer(ProcessJobs, null, Timeout.Infinite, Timeout.Infinite);
}
#endregion
#region Methods
public abstract Task JobTriggered(IJob job);
public abstract Task JobException(IJob job, Exception exception);
private void ProcessJobs(object state)
{
lock (_sync) {
IJob[] jobsToExecute = (from jobs in _jobs
where jobs.StartTime <= DateTimeOffset.Now && jobs.Enabled
orderby jobs.Priority
select jobs).ToArray();
if (jobsToExecute.Any()) {
foreach (IJob job in jobsToExecute) {
try {
JobTriggered(job).Wait(_executionTimeOut);
}
catch (AggregateException exception) {
JobException(job, exception).Wait(_executionTimeOut);
}
if (job.Repeat) {
job.StartTime = DateTimeOffset.Now.Add(job.Interval);
}
else {
_jobs.Remove(job);
}
if (_jobs.Count <= 0 || _paused)
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
}
else
{
_jobs.Sort((first, second) => first.StartTime.CompareTo(second.StartTime));
TimeSpan delay = _jobs[0].StartTime.Subtract(DateTimeOffset.Now);
long dueTime = Math.Min(Math.Max(100, (long)delay.TotalMilliseconds), (int)_baseInterval.TotalMilliseconds);
_timer.Change((int)dueTime, Timeout.Infinite);
}
}
}
}
}
public void PauseScheduler()
{
if (!_paused) {
_paused = !_paused;
}
}
public void ResumeScheduler()
{
if (_paused) {
_paused = !_paused;
}
}
public void AddJob(IJob job)
{
_jobs.Add(job);
_jobs.Sort((first, second) => first.StartTime.CompareTo(second.StartTime));
TimeSpan delay = _jobs[0].StartTime.Subtract(DateTimeOffset.Now);
long dueTime = Math.Min(Math.Max(100, (long)delay.TotalMilliseconds), (int)_baseInterval.TotalMilliseconds);
_timer.Change((int)dueTime, Timeout.Infinite);
}
public Task<T> AddJob<T>(IJob<T> job)
{
if (job.Repeat) {
throw new Exception("Repeatable jobs can't be awaited!");
}
_jobs.Add(job);
_jobs.Sort((first, second) => first.StartTime.CompareTo(second.StartTime));
TimeSpan delay = _jobs[0].StartTime.Subtract(DateTimeOffset.Now);
long dueTime = Math.Min(Math.Max(100, (long)delay.TotalMilliseconds), (int)_baseInterval.TotalMilliseconds);
_timer.Change((int)dueTime, Timeout.Infinite);
return job.TaskCompletionSource.Task;
}
public void RemoveJob(IJob job)
{
_jobs.RemoveAll(x => x.Equals(job));
}
#endregion
}
}
2) IJob
Interface
using System;
using System.Threading.Tasks;
namespace ATScheduler
{
public interface IJob
{
TimeSpan Interval { get; }
DateTimeOffset StartTime { get; set; }
int Priority { get; }
bool Repeat { get; }
bool Enabled { get; set; }
}
public interface IJob<T> : IJob
{
TaskCompletionSource<T> TaskCompletionSource { get; set; }
void Return(T result);
}
}
3) How to use:
using System;
using System.Threading.Tasks;
using ATScheduler;
namespace AdvancedScheduler
{
internal class Program : Scheduler
{
private static void Main()
{
Console.WriteLine("- Advanced yet simple Flow Task Scheduler!");
Console.WriteLine();
new Program().DoApplication();
Console.Read();
}
private async void DoApplication()
{
AddJob(new SimpleJob());
AddJob(new SimpleUndefinedJob());
Console.WriteLine("Awaitable job returned: {0}", await AddJob(new SimpleAwaitableJob()));
}
public override async Task JobTriggered(IJob job)
{
if (job is SimpleJob) {
await Task.Delay(1); //or time consumable task.
Console.WriteLine("Simple Job executed for {0} times", ((SimpleJob)job).ExecutionCounter);
((SimpleJob)job).ExecutionCounter++;
}
else if (job is SimpleAwaitableJob) {
((SimpleAwaitableJob) job).Return(5);
}
else {
throw new Exception("Undefined Job!");
}
}
public override async Task JobException(IJob job, Exception exception)
{
Console.WriteLine("{0} threw an exception of {1}", job.GetType().Name, exception.InnerException.Message);
}
private class SimpleAwaitableJob : IJob<int>
{
public SimpleAwaitableJob()
{
Priority = 1;
Repeat = false;
Enabled = true;
TaskCompletionSource = new TaskCompletionSource<int>();
Interval = TimeSpan.FromSeconds(5);
StartTime = DateTime.Now;
}
public TimeSpan Interval { get; private set; }
public DateTimeOffset StartTime { get; set; }
public int Priority { get; private set; }
public bool Repeat { get; private set; }
public bool Enabled { get; set; }
public void Return(int result)
{
TaskCompletionSource.SetResult(result);
}
public TaskCompletionSource<int> TaskCompletionSource { get; set; }
}
private class SimpleJob : IJob
{
public SimpleJob()
{
Priority = 0;
Repeat = true;
Enabled = true;
Interval = TimeSpan.FromSeconds(5);
StartTime = DateTime.Now.AddSeconds(10);
}
public TimeSpan Interval { get; private set; }
public DateTimeOffset StartTime { get; set; }
public int Priority { get; private set; }
public bool Repeat { get; private set; }
public bool Enabled { get; set; }
//We can extend it!
public int ExecutionCounter { get; set; }
}
private class SimpleUndefinedJob : IJob
{
public SimpleUndefinedJob()
{
Priority = 2;
Repeat = false;
Enabled = true;
Interval = TimeSpan.FromSeconds(5);
StartTime = DateTime.Now;
}
public TimeSpan Interval { get; private set; }
public DateTimeOffset StartTime { get; set; }
public int Priority { get; private set; }
public bool Repeat { get; private set; }
public bool Enabled { get; set; }
}
}
}
abstract
methods. \$\endgroup\$