/// <summary> /// Creates a new background operation. The background operations runs the given method at given intervals. Must be separately started. /// </summary> /// <param name="name">The name of the operation.</param> /// <param name="method">The method to invoke at given intervals.</param> /// <returns>A new background operation, that is not yet started.</returns> public TaskQueueBackgroundOperation <TDirective> CreateBackgroundOperation <TDirective> ( string name, Action <TaskProcessorJob, TDirective> method ) where TDirective : TaskQueueDirective { TaskQueueBackgroundOperation <TDirective> backgroundOperation; lock (TaskQueueBackgroundOperationManager._lock) { if (this.BackgroundOperations.ContainsKey(name)) { throw new ArgumentException( $"A background operation with the name {name} in queue {this.QueueId} could not be found.", nameof(name)); } // Create the background operation. backgroundOperation = new TaskQueueBackgroundOperation <TDirective> ( this, name, method, this.CancellationTokenSource ); // Add it to the dictionary. this.BackgroundOperations.Add(name, backgroundOperation); } // Return it. return(backgroundOperation); }
/// <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="BackgroundOperationTaskQueueDirective.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(TaskProcessorJob job) { // Sanity. if (null == job) { return; } // Ensure cancellation has not been requested. job.ThrowIfCancellationRequested(); // Update the progress of the task in the task queue. this.TaskProcessor.UpdateTaskAsAssignedToProcessor(job); // Sanity. if (null == job.Data?.Value) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no application task (queue: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } // Deserialize the background directive. var backgroundOperationDirective = job.GetTaskQueueDirective <BackgroundOperationTaskQueueDirective>(); if (null == backgroundOperationDirective) { // 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: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } // If we have a directive then extract it. var dir = backgroundOperationDirective.GetParsedInternalDirective(); // If it is a broadcast directive, then was it generated on the same server? // If so then ignore. if (dir is BroadcastDirective broadcastDirective) { if (broadcastDirective.GeneratedFromGuid == TaskQueueBackgroundOperationManager.CurrentServer.ServerID) { return; } } // Find the background operation to run. TaskQueueBackgroundOperation bo = null; lock (_lock) { if (false == this.BackgroundOperations.TryGetValue(backgroundOperationDirective.BackgroundOperationName, out bo)) { // We have no registered background operation to handle the callback. SysUtils.ReportErrorToEventLog ( $"No background operation found with name {backgroundOperationDirective.BackgroundOperationName}(queue: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } } // If it is recurring then use the ProcessingComplete event to schedule the next execution. if (bo.Recurring && bo.Interval.HasValue) { // 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.ProcessingCompleted += (s, op) => this.RunOnce ( bo.Name, DateTime.UtcNow.Add(bo.Interval.Value), dir ); } // Perform the action. try { // Delegate to the background operation. bo.RunJob(job, dir); } catch (Exception e) { // Exception. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateFailed, e.Message, false ); // TODO: throw? } }