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;
                }
            }
        }