Beispiel #1
0
        /// <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>
        /// <param name="options">The options for the display of the background operation in the dashboard.</param>
        /// <returns>A new background operation, that is not yet started.</returns>
        public TaskQueueBackgroundOperation <TDirective, TSecureConfiguration> CreateBackgroundOperation <TDirective>
        (
            string name,
            Action <TaskProcessorJobEx <BackgroundOperationTaskDirective, TSecureConfiguration>, TDirective> method
        )
            where TDirective : TaskDirective
        {
            TaskQueueBackgroundOperation <TDirective, TSecureConfiguration> backgroundOperation;

            lock (TaskQueueBackgroundOperationManager <TSecureConfiguration> ._lock)
            {
                if (this.BackgroundOperations.ContainsKey(name))
                {
                    throw new ArgumentException(
                              $"A background operation with the name {name} in queue {this.QueueId} already exists.",
                              nameof(name));
                }

                // Create the background operation.
                backgroundOperation = new TaskQueueBackgroundOperation <TDirective, TSecureConfiguration>
                                      (
                    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="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
            );
        }