private static IEnumerable <(IJobDetail job, ITrigger trigger)> GetRunnableJobs( IScheduledService svc, TelemetryClient telemetryClient) { foreach ((Func <Task> func, string schedule) in svc.GetScheduledJobs()) { string id = Guid.NewGuid().ToString(); IJobDetail job = JobBuilder.Create <FuncInvokingJob>() .WithIdentity(id) .UsingJobData( new JobDataMap { ["func"] = func, ["telemetryClient"] = telemetryClient }) .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity(id + "-Trigger") .WithCronSchedule(schedule) .StartNow() .Build(); yield return(job, trigger); } }
/// <summary> /// Will create a windows service which will be used to schedule available jobs. /// </summary> /// <param name="service"></param> public static void CreateWindowsService(IScheduledService service) { if (Running) { throw new InvalidOperationException("Service has already been created. Only one service may be run at time. Consider using jobs for multiple"); } if (service.FindJobs) { service.Jobs = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => typeof(IScheduledJob).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) .Select(c => Activator.CreateInstance(c) as IScheduledJob); } HostFactory.Run(x => { x.Service <IScheduledService>(s => { s.WhenStarted(a => a.Start()); s.WhenStopped(a => a.Stop()); s.ConstructUsing(a => service); foreach (var job in service.Jobs) { s.ScheduleQuartzJob(q => q.WithJob(() => JobBuilder.Create(job.GetType()).Build()) .AddTrigger(() => job.Schedule)); } }); x.RunAsLocalSystem() .DependsOnEventLog() .StartAutomatically() .EnableServiceRecovery(rc => rc.RestartService(1)); x.SetServiceName(service.ServiceName); x.SetDisplayName(service.DisplayName); x.SetDescription(service.Description); }); }
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); } }