protected virtual void Reschedule() { if (IsDisposed) { return; } if (SchedulingItems.Count == 0) { NextExecution = null; Timer.Change(Timeout.Infinite, Timeout.Infinite); LastTimerInterval = Timeout.Infinite; } else { var executionTime = SchedulingItems[0].NextExecution; DateTimeOffset now = SchedulingTimeHelpers.Now(); TimeSpan delay = executionTime.Value.Subtract(now); long dueTime = System.Math.Max(MinJobInterval, (long)delay.TotalMilliseconds); dueTime = System.Math.Min(dueTime, SelfTestInterval); NextExecution = SchedulingTimeHelpers.Now().AddMilliseconds(dueTime); Timer.Change(dueTime, Timeout.Infinite); LastTimerInterval = dueTime; } LastTimerRun = SchedulingTimeHelpers.Now(); }
/// <summary> /// Updates the internal state after an execution, and updates the /// <see cref="LastJobEvaluation"/> and <see cref="NextExecution"/> /// values. If the job is not supposed to run anymore, the /// <see cref="NextExecution"/> property is set to null. /// </summary> protected virtual void UpdateState() { LastJobEvaluation = SchedulingTimeHelpers.Now(); lock (ManagedJob.SynchronizationToken) { if (ManagedJob.SchedulingState == SchedulingItemStateType.Aborted) { NextExecution = null; return; } if (RemainingExecutions.HasValue) { //only decrease the loop counter if the job is not paused if (ManagedJob.SchedulingState == SchedulingItemStateType.Running) { RemainingExecutions--; } //we're done if we peformend the last loop if (RemainingExecutions == 0) { //we have a loop, and completed it ManagedJob.SchedulingState = SchedulingItemStateType.Done; NextExecution = null; return; } } //if there is no reoccurrence interval, we cannot calculate a new run //-> cancel if (!ManagedJob.Interval.HasValue) { ManagedJob.SchedulingState = SchedulingItemStateType.Aborted; NextExecution = null; return; } //schedule next execution - even if this is beyond expiration //(we don't cancel as long as expiration happened) NextExecution = (NextExecution.Value).Add(ManagedJob.Interval.Value); //if the next job is beyond expiration, reset again if (ManagedJob.ExpirationTime.HasValue && LastJobEvaluation.Value > ManagedJob.ExpirationTime) { ManagedJob.SchedulingState = SchedulingItemStateType.Done; NextExecution = null; } } }
protected virtual void RunPendingJobs() { var now = SchedulingTimeHelpers.Now(); var dueJobs = new List <SchedulingItemContext>(); SchedulingItemContext currentJob = null; for (int i = 0; i < SchedulingItems.Count; i++) { var job = SchedulingItems[i]; if (job.NextExecution.Value > now) { break; } dueJobs.Add(job); } try { for (var i = dueJobs.Count - 1; i >= 0; i--) { currentJob = dueJobs[i]; currentJob.ExecuteAsync(this); if (currentJob.NextExecution.HasValue) { IsSorted = false; } else { SchedulingItems.RemoveAt(i); } } } catch (Exception e) { if (!SubmitJobException(currentJob.ManagedJob, e)) { throw; } } }
/// <summary> /// Initializes a new instance of the <see cref="T:System.Object"/> class. /// </summary> /// <exception cref="ArgumentNullException">If one of the parameters is /// is a null reference.</exception> /// <exception cref="InvalidOperationException">If the configuration does not allow /// proper scheduling, e.g. because several loops were specified, but the inverval is /// missing.</exception> public SchedulingItemContext(SchedulingItem managedJob, Action <SchedulingItem> callbackAction) { if (managedJob == null) { throw new ArgumentNullException("managedJob"); } if (callbackAction == null) { throw new ArgumentNullException("callbackAction"); } //make sure we have an interval if we have more than one loop TimeSpan?interval = managedJob.Interval; if (interval == null) { if (managedJob.Loops == null || managedJob.Loops.Value > 1) { string msg = "Job [{0}] is invalid: Specifiy either a single run, or a loop interval."; msg = String.Format(msg, managedJob.Id); throw new InvalidOperationException(msg); } } ManagedJob = managedJob; CallbackAction = callbackAction; NextExecution = managedJob.StartTime; //adjust starting time if the initial time was in the past, //but rather start immediately var now = SchedulingTimeHelpers.Now(); if (NextExecution.Value < now) { NextExecution = now; } RemainingExecutions = managedJob.Loops; }
public virtual void Submit(SchedulingItem schedulingItem, Action <SchedulingItem> callback) { if (schedulingItem == null) { throw new ArgumentNullException("schedulingItem"); } if (callback == null) { throw new ArgumentNullException("callback"); } var interval = schedulingItem.Interval; if (interval.HasValue && interval.Value.TotalMilliseconds < MinJobInterval) { string msg = "Interval of {0} ms is too small - a minimum interval of {1} ms is accepted."; msg = String.Format(msg, interval.Value.TotalMilliseconds, MinJobInterval); throw new InvalidOperationException(msg); } var context = new SchedulingItemContext(schedulingItem, callback); lock (SyncRoot) { if (NextExecution == null || context.NextExecution <= NextExecution.Value) { SchedulingItems.Insert(0, context); if (NextExecution == null || NextExecution.Value.Subtract(SchedulingTimeHelpers.Now()).TotalMilliseconds > MinJobInterval) { Reschedule(); } } else { SchedulingItems.Add(context); IsSorted = false; } } }
protected virtual void VerifySystemTime() { if (LastTimerInterval == Timeout.Infinite) { return; } var now = SchedulingTimeHelpers.Now(); var pauseDuration = now.Subtract(LastTimerRun); var timeDivergence = pauseDuration.TotalMilliseconds - LastTimerInterval; if (timeDivergence > 1000 || timeDivergence < 1000) { bool changeExpirationTime = ReschedulingType == ReschedulingType.RescheduleNextExecutionAndExpirationTime; SchedulingItems.ForEach(jc => { jc.NextExecution = jc.NextExecution.Value.AddMilliseconds(timeDivergence); if (changeExpirationTime && jc.ManagedJob.ExpirationTime.HasValue) { jc.ManagedJob.ExpirationTime = jc.ManagedJob.ExpirationTime.Value.AddMilliseconds(timeDivergence); } }); } }
/// <summary> /// Invokes the managed job's <see cref="CallbackAction"/> through /// the thread pool, and updates the job's internal state. /// </summary> public virtual void ExecuteAsync(SchedulingService scheduler) { //only execute if the job is neither aborted, expired, or paused if (ManagedJob.SchedulingState == SchedulingItemStateType.Running && (ManagedJob.ExpirationTime == null || ManagedJob.ExpirationTime >= SchedulingTimeHelpers.Now())) { ThreadPool.QueueUserWorkItem(s => { try { CallbackAction(ManagedJob); } catch (Exception e) { //do not reference the scheduler instance to avoid closure SchedulingService sch = (SchedulingService)s; if (!sch.SubmitJobException(ManagedJob, e)) { throw; } } }, scheduler); } UpdateState(); }