private async Task HandleScheduledService(IScheduledService service, CloudBlob blob, CancellationToken token) { const string lastExecutionMetadata = "LastExecution"; const string nextExecutionMetadata = "NextExecution"; while (!token.IsCancellationRequested) { var nextExecution = DateTime.MinValue; // poll: we need to take action if the blob doesn't exist yet, doesn't have a next execution time, or is scheduled to run ASAP while (!await blob.ExistsAsync() || String.IsNullOrEmpty(blob.Metadata[nextExecutionMetadata]) || (nextExecution = DateTime.Parse(blob.Metadata[nextExecutionMetadata])) <= DateTime.UtcNow) { try { // try to lock the blob using (var @lock = await CloudLock.LockAsync(blob)) { if (@lock != null) { // we acquired the lock, which means we're responsible for running the service var lastExecution = String.IsNullOrEmpty(blob.Metadata[lastExecutionMetadata]) ? null : (DateTime?)DateTime.Parse(blob.Metadata[lastExecutionMetadata]); var thisExecution = DateTime.UtcNow; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, @lock.CancellationToken)) nextExecution = await service.Run(lastExecution, cts.Token) ?? DateTime.MaxValue; blob.Metadata[lastExecutionMetadata] = thisExecution.ToString(CultureInfo.InvariantCulture); blob.Metadata[nextExecutionMetadata] = nextExecution.ToString(CultureInfo.InvariantCulture); await blob.SetMetadataAsync(@lock.LeaseId); } else { // wait 5 secs to see if it has finished yet await TaskEx.Delay(5000); // since we're going to loop again, we should verify no one wants us to stop looping if (token.IsCancellationRequested) break; } } } catch (OperationCanceledException) { // if the lock was lost, this will retry everything // if the service was cancelled, this will now bail out entirely break; } } if (token.IsCancellationRequested) break; // either (a) the service didn't need to run, (b) we ran the service, or (c) someone else ran the service // now, wait till the next execution time var waitTime = nextExecution - DateTime.UtcNow; if (waitTime < TimeSpan.Zero) continue; if (waitTime > TimeSpan.FromMinutes(5)) waitTime = TimeSpan.FromMinutes(5); await TaskEx.Delay(waitTime); } }