/// <summary> /// Marks the <paramref name="job"/> to have the completed state of <paramref name="targetState"/>. /// </summary> /// <param name="job">The job to update.</param> /// <param name="targetState">The final completed state.</param> /// <param name="exception">The exception, if the state is failed.</param> protected void CompleteJob ( AppTasks.ITaskProcessingJob <BackgroundOperationTaskDirective> job, MFTaskState targetState, Exception exception = null ) { // Skip nulls. if (null == job) { return; } // Update the task. try { // Build up the task information with new data. var taskInformation = new TaskInformation(job.GetStatus()?.Data); taskInformation.CurrentTaskState = targetState; taskInformation.Completed = DateTime.Now; taskInformation.LastActivity = DateTime.Now; if (exception != null) { taskInformation.StatusDetails = exception.Message; } // Update the task information. job.Update(taskInformation); } catch { #if DEBUG throw; #endif } }
/// <summary> /// Handles when a job from a task requires processing. /// Note that all tasks/jobs in the queue use the same task type ID, /// so the directive <see cref="BackgroundOperationTaskDirective.BackgroundOperationName"/> /// is used to identify which tasks should be executed by /// which background operation. /// </summary> /// <param name="job">The job to process.</param> protected virtual void ProcessJobHandler(AppTasks.ITaskProcessingJob <BackgroundOperationTaskDirective> job) { // Sanity. if (null == job) { return; } // Ensure cancellation has not been requested. job.ThrowIfTaskCancellationRequested(); // What is the current state? var state = job.GetStatus(); // Sanity. if (null == job.Directive) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no directive (queue: {this.QueueId}, task type: {job.TaskInfo.TaskType}, task id: {job.TaskInfo.TaskID})." ); return; } // Check that we know the job this was associated with. if (string.IsNullOrWhiteSpace(job.Directive.BackgroundOperationName)) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no background operation name loaded (queue: {this.QueueId}, task type: {job.TaskInfo.TaskType}, task id: {job.TaskInfo.TaskID})." ); return; } // If we have a directive then extract it. var dir = job.Directive.GetParsedInternalDirective(); // Find the background operation to run. TaskQueueBackgroundOperation <TSecureConfiguration> bo = null; lock (_lock) { if (false == this.BackgroundOperations.TryGetValue(job.Directive.BackgroundOperationName, out bo)) { // We have no registered background operation to handle the callback. SysUtils.ReportErrorToEventLog ( $"No background operation found with name {job.Directive.BackgroundOperationName} (queue: {this.QueueId}, task type: {job.TaskInfo.TaskType}, task id: {job.TaskInfo.TaskID})." ); return; } } // Should we repeat? DateTime?nextRun = null; switch (bo.RepeatType) { case TaskQueueBackgroundOperationRepeatType.Interval: // Add the interval to the current datetime. if (bo.Interval.HasValue) { nextRun = DateTime.UtcNow.Add(bo.Interval.Value); } break; case TaskQueueBackgroundOperationRepeatType.Schedule: // Get the next execution time from the schedule. nextRun = bo.Schedule?.GetNextExecution(DateTime.Now); break; } // If we have a next run time then re-run. if (null != nextRun) { // Bind to the completed event ( called always ) of the job. // That way even if the job is canceled, fails, or finishes successfully // ...we always schedule the next run. job.Completed += (s, op) => { // Ensure that if two threads both run this at once we don't end up with a race condition. lock (_lock) { // Cancel any future executions (we only want the single one created below). this.CancelFutureExecutions(bo.Name); // Now schedule it to run according to the interval. this.RunOnce ( bo.Name, nextRun.Value.ToUniversalTime(), dir ); } }; } // Bind to the life-cycle events. job.Succeeded += (sender, op) => CompleteJob(job, MFTaskState.MFTaskStateCompleted); job.Failed += (sender, args) => CompleteJob(job, MFTaskState.MFTaskStateFailed, args.Exception); // Perform the action. // Mark it as started. job.Update ( new TaskInformation() { Started = DateTime.Now, LastActivity = DateTime.Now, CurrentTaskState = MFTaskState.MFTaskStateInProgress } ); // NOTE: this should not have any error handling around it here unless it re-throws the error // Catching the exception here can result in job.ProcessingFailed not being called // Delegate to the background operation. bo.RunJob ( // The TaskProcessorJobEx class wraps the job and allows easy updates. new TaskProcessorJobEx <BackgroundOperationTaskDirective, TSecureConfiguration>() { Job = job, TaskQueueBackgroundOperationManager = this }, dir ); }