Job Scheduler Implementation - c#

Well i have created a job scheduler that has many capabilities; However i really want to use .Net 4.5 Async/Await feature with it in order to wait for a job to finish executing.
-Code
Scheduler.cs
public abstract class Scheduler
{
#region Fields && Properties
private readonly List<Job> _jobs = new List<Job>();
private readonly Random _rand = new Random();
private Job _currentlyExecutingJob;
private Thread _workingThread;
public bool? Parallel { get; private set; }
public DateTimeOffset NextExecutionTime { get; private set; }
public string ID { get; set; }
public abstract Task JobTrigger(Job job);
public abstract void UnobservedException(Exception exception, Job job);
#endregion
#region Ctor
protected Scheduler(bool parallel) { Parallel = parallel; }
#endregion
#region Fluent
public Scheduler Start()
{
if (Equals(_workingThread, null)) {
_workingThread = new Thread(ReviewJobs);
_workingThread.Start();
}
return this;
}
public Scheduler Stop()
{
if (!Equals(_workingThread, null)) {
_workingThread.Abort();
_workingThread = null;
}
return this;
}
#endregion
#region Events
private void ReviewJobs()
{
while (!Equals(_workingThread, null)) {
IEnumerable<Job> jobsToExecute = from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job;
if (!jobsToExecute.Any()) {
Thread.Sleep(100);
continue;
}
try {
foreach (Job job in jobsToExecute) {
Job o = _currentlyExecutingJob = job;
if (Parallel != null && (bool)Parallel) {
JobTrigger(o);
} else {
JobTrigger(o).Wait();
}
if (!o.Repeat)
_jobs.Remove(o);
else if (o.Interval != null)
o.NextExecutionTime = DateTimeOffset.Now.Add((TimeSpan)(o.RandomizeExecution
? TimeSpan.FromSeconds(_rand.Next((int)((TimeSpan)o.Interval).TotalSeconds, ((int)((TimeSpan)o.Interval).TotalSeconds + 30)))
: o.Interval));
}
} catch (Exception exception) {
UnobservedException(exception, _currentlyExecutingJob);
} finally {
NextExecutionTime = (from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job.NextExecutionTime).FirstOrDefault();
}
}
}
#endregion
#region Helper Methods
public Job GetJob(string id) { return _jobs.Find(job => job.ID == id); }
public Job GetJob(Job job) { return _jobs.Find(x => x == job); }
public IEnumerable<Job> GetAllJobs() { return _jobs; }
public void AddJob(Job job, bool overwrite = false)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
if (overwrite) {
_jobs.RemoveAll(jobs => jobs == existingJob);
_jobs.Add(job);
} else {
_jobs.Add(job);
}
} else {
_jobs.Add(job);
}
}
public bool RemoveJob(string id)
{
Job existingJob = GetJob(id);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public bool RemoveJob(Job job)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public void RemoveAllJobs() { _jobs.RemoveRange(0, _jobs.Count); }
#endregion
}
Job.cs
public class Job
{
public string ID { get; set; }
public TimeSpan Interval { get; private set; }
public DateTimeOffset NextExecutionTime { get; set; }
public int Priority { get; set; }
public bool Repeat { get; private set; }
public bool RandomizeExecution { get; set; }
public object Data { get; set; }
#region Fluent
public Job RunOnceAt(DateTimeOffset executionTime)
{
NextExecutionTime = executionTime;
Repeat = false;
return this;
}
public Job RepeatFrom(DateTimeOffset executionTime, TimeSpan interval)
{
NextExecutionTime = executionTime;
Interval = interval;
Repeat = true;
return this;
}
#endregion
}
-Usage
public class SchedulerUsage : Scheduler
{
public SchedulerUsage(bool parallel) : base(parallel) {
}
public override async Task JobTrigger(Job job)
{
switch (job.ID) {
//Handle Jobs
}
}
public override void UnobservedException(Exception exception, Job job)
{
//Handle Exceptions
}
/// <summary>
/// Example of adding job
/// </summary>
public void ExampleUsage()
{
Job job = new Job
{
ID = "ID",
Data = "ATTACH SOME DATA"
}.RunOnceAt(DateTimeOffset.Now.AddSeconds(7));
//Add the job... [HERE IS WHAT I WANT TO ACHIVE]
/*await*/base.AddJob(job);
//Start the scheduler...
base.Start();
}
}
Question: How to use async/await to await the execution of a job in my implementation above.
NB: I am sorry if my question seems to be a bit complicated but it is very necessary for me so please be patience with me.

Related

How to abort tacked tasks in C#?

I am trying to create a task tracker in C#. Each task has a unique id/name and a Task that would run "one instance per id can run at the same time.
I created the following class to track tasks
public partial class ProgressTracker : IProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
public bool TryStart(string name, Task task)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsRunning(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(!progress.Job.IsCanceled && !progress.Job.IsCompleted)
{
// How can I abort progress.Job task
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}
Here is the class that contains the data about the tracked task.
public sealed class TrackedProgress
{
internal DateTime StartedAt { get; set; }
public Task Job { get; set; }
public int? Duration { get; set; }
public TrackedProgress(Task job)
{
StartedAt = DateTime.Now;
Job = job;
}
public TrackedProgress(DateTime startedAt, Task job)
{
StartedAt = startedAt;
Job = job;
}
}
Since I am tracking a Task, how can I abort it upon request?
You can add cts to the TrackedProgress class:
public sealed class TrackedProgress
{
internal DateTime StartedAt { get; set; }
public Task Job { get; set; }
public CancellationTokenSource Cts { get; }
public int? Duration { get; set; }
public TrackedProgress(Task job, CancellationTokenSource cts)
{
StartedAt = DateTime.Now;
Job = job;
Cts = cts;
}
public TrackedProgress(DateTime startedAt, Task job, CancellationTokenSource cts)
{
StartedAt = startedAt;
Job = job;
Cts = cts;
}
}
And few changes in ProgressTracker:
public partial class ProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
//Here - add the cts
public bool TryStart(string name, Task task, CancellationTokenSource cts)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task, cts));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsRunning(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(!progress.Job.IsCanceled && !progress.Job.IsCompleted)
{
// Cancelling the task
progress.Cts.Cancel();
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}
Of course you need to make sure you handle cancellation inside the task body too!
After reading the comments, I tweaked my consumer class which import data which may take time. This class will accept a task and a cancellation token. The cancellation token is tracked along with the Task
The tracker class class now looks like this
internal sealed class TrackedProgress
{
public DateTime StartedAt { get; private set; }
public Task Job { get; private set; }
public CancellationTokenSource CancellationToken { get; private set; }
public int? Duration { get; private set; }
public TrackedProgress(Task job, CancellationToken cancellationToken)
: this(DateTime.Now, job, cancellationToken)
{
}
public TrackedProgress(DateTime startedAt, Task job, CancellationToken cancellationToken)
{
StartedAt = startedAt;
Job = job;
CancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
}
public void Abort()
{
CancellationToken?.Cancel();
}
public bool IsRunning()
{
if (Job == null)
{
return false;
}
return !Job.IsCompleted && !Job.IsCanceled;
}
}
The tracker class was modified to this
public partial class ProgressTracker : IProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
public bool TryStart(string name, Task task, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task, cancellationToken));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
if(progress.IsRunning())
{
return false;
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsTracked(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(progress.IsRunning())
{
progress.Abort();
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}

AKKA.Net: How to send message from supervisor to child on Actor Restart

Considering this code:
public class ParentActor : ReceiveActor
{
public ParentActor()
{
for( i = 1; i <= 5; i++)
var childActor = Context.ActorOf<ChildActor>($"Child{i}");
childActor.Tell( new LoadData(i));
}
}
public class ChildActor : ReceiveActor
{
public ChildActor()
{
Receive<LoadData>(msg =>
{
var data = SomeRiskyOverNetworkCall(msg.AccountId);
});
}
}
public class LoadData
{
public int AccountId { get; }
public LoadData(int accountId)
{
AccountId = accountId;
}
}
The child actor performs some risky operation that fails from time to time
The call is using some parameters that are passed from parent/supervisor actor after creation of the child.
How to "supervise" this scenario? I need the same LoadData (with the same parameters) message to be processed after restart.
You can use pre-restart hook with some supervisor strategy. below is a simple example of the same.
https://getakka.net/articles/actors/fault-tolerance.html#creating-a-supervisor-strategy
using System;
using System.Threading;
using Akka.Actor;
namespace AkkaConsoleSimple
{
public class Start
{
public Start()
{
}
}
public class DoSomething
{
public DoSomething(int who)
{
Who = who;
}
public int Who { get; private set; }
}
public class FailedMessage
{
public FailedMessage(object who)
{
Who = who;
}
public object Who { get; private set; }
}
public class Child : ReceiveActor
{
public Child()
{
Receive<DoSomething>(msg =>
{
Console.WriteLine($"getting message no {msg.Who}");
if (msg.Who == 10)
{
throw new StackOverflowException();
}
});
}
protected override void PreRestart(Exception cause, object message)
{
Sender.Tell(new FailedMessage(message));
Self.Tell(message);
base.PreRestart(cause, message);
}
}
public class Parent : ReceiveActor
{
public Parent()
{
Receive<Start>(greet =>
{
var child = Context.ActorOf<Child>();
for (int i = 0; i < 11; i++)
{
var msg = new DoSomething(i);
child.Tell(msg);
}
});
Receive<FailedMessage>(failed => Console.WriteLine(failed.Who));
}
protected override SupervisorStrategy SupervisorStrategy()
{
return new OneForOneStrategy(
maxNrOfRetries: 10,
withinTimeRange: TimeSpan.FromMinutes(1),
localOnlyDecider: ex =>
{
switch (ex)
{
case StackOverflowException ae:
return Directive.Restart;
default:
return Directive.Escalate;
}
});
}
}
class Program
{
static void Main(string[] args)
{
var system = ActorSystem.Create("MySystem");
var greeter = system.ActorOf<Parent>("parent");
greeter.Tell(new Start());
Console.Read();
}
}
}

How to design a processor that loads objects from a runtime configuration

I am implementing a scheduler. As part of its core logic it processes a custom object Schedule. Basically it iterates over an array of schedules and try to process it. The problem is who ever creates a Schedule needs to register it with the container using an ISchedule interface. My Scheduler then pulls all the ISchedule references from the container. SO far this is working but it does not have the flexibility of loading the schedules runtime. What design and implementation I can adapt to implement a Scheduler that can load those Schedules run time. I am giving some sample code.
Something that is coming to my mind is having the developers writing a json representation of the Schedules and put that inside a config or implementing an endpoint that returns that config to the Scheduler. But can I avoid this? I want the Scheduler to be completely agonistic of developer code.
You can use the factory to register information about schedule classes. And dynamically change the call interval by finding the schedule by Id.
You register in the container: <IScheduleFactory,ScheduleFactory> and <IScheduleManager,ScheduleManager>
public interface ISchedule
{
public string Id { get; set; }
public TimeSpan Interval { get; set; }
public DateTime? LastExecution { get; set; }
public bool CanStart { get; }
void Start();
void Stop();
}
public sealed class Schedule : ISchedule
{
public string Id { get; set; }
public TimeSpan Interval { get; set; }
public DateTime? LastExecution { get; set; }
public bool CanStart {
get
{
lock (_sync)
{
return !LastExecution.HasValue || LastExecution.Value.Add(Interval) >= DateTime.UtcNow;
}
}
}
private readonly object _sync = new object();
public void Start()
{
lock (_sync)
{
if (!LastExecution.HasValue || LastExecution.Value.Add(Interval) >= DateTime.UtcNow)
{
// DO WORK
LastExecution = DateTime.UtcNow;
}
}
}
public void Stop()
{
throw new NotImplementedException();
}
}
public interface IScheduleFactory
{
ISchedule Create();
}
public sealed class ScheduleFactory: IScheduleFactory
{
private readonly IScheduleManager _manager;
public ScheduleFactory(IScheduleManager manager)
{
_manager = manager;
}
public ISchedule Create()
{
var schedule = new Schedule();
_manager.Register(schedule);
return schedule;
}
}
public interface IScheduleManager
{
void Register(ISchedule schedule);
ISchedule Get(string id);
void Start();
void Stop();
}
public sealed class ScheduleManager : IScheduleManager
{
private readonly Dictionary<string,ISchedule> _items = new Dictionary<string, ISchedule>();
private readonly object _sync = new object();
public void Register(ISchedule schedule)
{
lock (_sync)
{
if (_items.ContainsKey(schedule.Id))
_items.Add(schedule.Id, schedule);
}
}
public ISchedule Get(string id)
{
lock (_sync)
{
if (_items.ContainsKey(id))
return _items[id];
}
return null;
}
private bool _isStart;
public void Start()
{
_isStart = true;
while (_isStart)
{
ISchedule[] array = null;
lock (_sync)
{
array = _items.Values.ToArray();
}
foreach (var schedule in array)
{
if (schedule.CanStart)
Task.Factory.StartNew(()=>schedule.Start());
}
}
}
public void Stop()
{
_isStart = false;
}
}

Correct way to get results from a blocking collection

I have 50 IMountCmd objects from one or more threads and drop them in a blocking collection. Each is executed and some get results or errors. They are put into a ConcurrentDictionary where I loop for ContainsKey and return the objects. Does this seems thread safe and correct way to process a blocking queue?
public class CmdGetAxisDegrees : IMountCommand
{
public long Id { get; }
public DateTime CreatedUtc { get; private set; }
public bool Successful { get; private set; }
public Exception Exception { get; private set; }
public dynamic Result { get; private set; }
private readonly AxisId _axis;
public CmdGetAxisDegrees(long id, AxisId axis)
{
Id = id;
_axis = axis;
CreatedUtc = HiResDateTime.UtcNow;
Successful = false;
Queues.AddCommand(this);
}
public void Execute(Actions actions)
{
try
{
Result = actions.GetAxisDegrees(_axis);
Successful = true;
}
catch (Exception e)
{
Successful = false;
Exception = e;
}
}
}
private static void ProcessCommandQueue(IMountCommand command)
{
command.Execute(_actions);
if (command.Id > 0)
{
_resultsDictionary.TryAdd(command.Id, command);
}
}
public static IMountCommand GetCommandResult(long id)
{
IMountCommand result;
while (true)
{
if (!_resultsDictionary.ContainsKey(id)) continue;
var success = _resultsDictionary.TryRemove(id, out result);
if (!success)
{
//log
}
break;
}
return result;
}
static Queues()
{
_actions = new Actions();
_resultsDictionary = new ConcurrentDictionary<long, IMountCommand>();
_commandBlockingCollection = new BlockingCollection<IMountCommand>();
Task.Factory.StartNew(() =>
{
foreach (var command in _commandBlockingCollection.GetConsumingEnumerable())
{
ProcessCommandQueue(command);
}
});
}
public interface IMountCommand
{
long Id { get; }
DateTime CreatedUtc { get; }
bool Successful { get; }
Exception Exception { get; }
dynamic Result { get; }
void Execute(Actions actions);
}

Why isn't the Rhino Mock expectation met?

Here is code:
public interface IAccessPoint
{
int BackHaulMaximum { get; set; }
bool BackHaulMaximumReached();
void EmailNetworkProvider();
}
public class AccessPoint : IAccessPoint
{
private IMailProvider Mailer { get; set; }
public AccessPoint(IMailProvider provider)
{
this.Mailer = provider ?? new DefaultMailProvider();
}
public int BackHaulMaximum { get; set; }
public bool BackHaulMaximumReached()
{
if (BackHaulMaximum > 80)
{
EmailNetworkProvider();
return true;
}
return false;
}
public void EmailNetworkProvider()
{
this.Mailer.SendMail();
}
}
public interface IMailProvider
{
void SendMail();
}
public class DefaultMailProvider : IMailProvider
{
public void SendMail()
{
}
}
// Here is the Test, It is not calling EmailNetworkProvider which calls SendMail()
[TestFixture]
public class Tests
{
[Test]
public void NetworkProviderShouldBeEmailedWhenBackHaulMaximumIsReached()
{
var mailerMock = MockRepository.GenerateMock<IMailProvider>();
mailerMock.Expect(x => x.SendMail());
var accessPoint = new AccessPoint(mailerMock);
accessPoint.BackHaulMaximum = 81;
Assert.IsTrue(accessPoint.BackHaulMaximumReached());
mailerMock.VerifyAllExpectations();
}
}
Any improvement if you use this test?
[Test]
public void NetworkProviderShouldBeEmailedWhenBackHaulMaximumIsReached()
{
var mailerMock = MockRepository.GenerateStub<IMailProvider>();
var accessPoint = new AccessPoint(mailerMock);
accessPoint.BackHaulMaximum = 81;
var actual = accessPoint.BackHaulMaximumReached();
Assert.AreEqual(true, actual);
mailerMock.AssertWasCalled(x => x.SendMail());
}
As a side-note, BackhaulMaximumReached() is kind of a bizarre design. No notification will be made unless a consumer checks whether the back haul maximum was reached, regardless of the value of BackHaulMaximum.
It is semantically confusing to comingle commands and queries in this way.

Categories