public async Task Consume(CancellationToken cancellationToken = default) { var uploadTime = DateTimeOffset.MinValue; var channelReader = _progressChannel.Reader; while (await channelReader.WaitToReadAsync(cancellationToken)) { if (channelReader.TryRead(out TaskContext context)) { // if a uncompleted context is produced, and before this context is consumed, it is updated to completed. // there are two context in this channel, since the context is a object, so the value of them are the same, both are completed. // when the first context is consumed, its data are merged to job and it is removed from the job, so we do nothing for the second context. if (_job.RunningTasks.ContainsKey(context.Id)) { _job.RunningTasks[context.Id] = context; if (context.IsCompleted) { _job.TotalResourceCounts = _job.TotalResourceCounts.ConcatDictionaryCount(context.SearchCount); _job.SkippedResourceCounts = _job.SkippedResourceCounts.ConcatDictionaryCount(context.SkippedCount); _job.ProcessedResourceCounts = _job.ProcessedResourceCounts.ConcatDictionaryCount(context.ProcessedCount); if (context.FilterScope == FilterScope.Group) { foreach (var(patientId, versionId) in context.SearchProgress.PatientVersionId) { _job.PatientVersionId[patientId] = versionId; } } _job.RunningTasks.Remove(context.Id); } } if (uploadTime.AddSeconds(JobConfigurationConstants.UploadDataIntervalInSeconds) < DateTimeOffset.UtcNow) { uploadTime = DateTimeOffset.UtcNow; // Upload to job store. await _jobStore.UpdateJobAsync(_job, cancellationToken); _logger.LogInformation("Update job {jobId} progress successfully.", _job.Id); } } } await _jobStore.UpdateJobAsync(_job, cancellationToken); }
/// <summary> /// Resume an active job or trigger new job from job store. /// and execute the job. /// </summary> /// <param name="cancellationToken">cancellation token.</param> /// <returns>Completed task.</returns> public async Task RunAsync(CancellationToken cancellationToken = default) { _logger.LogInformation("Job starts running."); // Acquire an active job from the job store. var job = await _jobStore.AcquireActiveJobAsync(cancellationToken) ?? await CreateNewJobAsync(cancellationToken); if (job == null) { _logger.LogWarning("Job has been scheduled to end."); // release job lock Dispose(); } else { _logger.LogInformation($"The running job id is {job.Id}"); // Update the running job to job store. // For new/resume job, add the created job to job store; For active job, update the last heart beat. await _jobStore.UpdateJobAsync(job, cancellationToken); try { job.Status = JobStatus.Running; await _jobExecutor.ExecuteAsync(job, cancellationToken); job.Status = JobStatus.Succeeded; await _jobStore.CompleteJobAsync(job, cancellationToken); } catch (Exception exception) { job.Status = JobStatus.Failed; job.FailedReason = exception.ToString(); await _jobStore.CompleteJobAsync(job, cancellationToken); _logger.LogError(exception, "Process job '{jobId}' failed.", job.Id); throw; } } }