1
+ namespace CronBackgroundWorker . Cron ;
2
+
3
+ public sealed class CronScheduler : BackgroundService
4
+ {
5
+ private readonly IServiceProvider _serviceProvider ;
6
+ private readonly IReadOnlyCollection < CronRegistryEntry > _cronJobs ;
7
+
8
+ public CronScheduler (
9
+ IServiceProvider serviceProvider ,
10
+ IEnumerable < CronRegistryEntry > cronJobs )
11
+ {
12
+ // Use the container
13
+ _serviceProvider = serviceProvider ;
14
+ _cronJobs = cronJobs . ToList ( ) ;
15
+ }
16
+
17
+ protected override async Task ExecuteAsync ( CancellationToken stoppingToken )
18
+ {
19
+ // Create a timer that has a resolution less than 60 seconds
20
+ // Because cron has a resolution of a minute
21
+ // So everything under will work
22
+ using var tickTimer = new PeriodicTimer ( TimeSpan . FromSeconds ( 30 ) ) ;
23
+
24
+ // Create a map of the next upcoming entries
25
+ var runMap = new Dictionary < DateTime , List < Type > > ( ) ;
26
+ while ( await tickTimer . WaitForNextTickAsync ( stoppingToken ) )
27
+ {
28
+ // Get UTC Now with minute resolution (remove microseconds and seconds)
29
+ var now = UtcNowMinutePrecision ( ) ;
30
+
31
+ // Run jobs that are in the map
32
+ RunActiveJobs ( runMap , now , stoppingToken ) ;
33
+
34
+ // Get the next run for the upcoming tick
35
+ runMap = GetJobRuns ( ) ;
36
+ }
37
+ }
38
+
39
+ private void RunActiveJobs ( IReadOnlyDictionary < DateTime , List < Type > > runMap , DateTime now , CancellationToken stoppingToken )
40
+ {
41
+ if ( ! runMap . TryGetValue ( now , out var currentRuns ) )
42
+ {
43
+ return ;
44
+ }
45
+
46
+ foreach ( var run in currentRuns )
47
+ {
48
+ // We are sure (thanks to our extension method)
49
+ // that the service is of type ICronJob
50
+ var job = ( ICronJob ) _serviceProvider . GetRequiredService ( run ) ;
51
+
52
+ // We don't want to await jobs explicitly because that
53
+ // could interfere with other job runs
54
+ job . Run ( stoppingToken ) ;
55
+ }
56
+ }
57
+
58
+ private Dictionary < DateTime , List < Type > > GetJobRuns ( )
59
+ {
60
+ var runMap = new Dictionary < DateTime , List < Type > > ( ) ;
61
+ foreach ( var cron in _cronJobs )
62
+ {
63
+ var utcNow = DateTime . UtcNow ;
64
+ var runDates = cron . CrontabSchedule . GetNextOccurrences ( utcNow , utcNow . AddMinutes ( 1 ) ) ;
65
+ if ( runDates is not null )
66
+ {
67
+ AddJobRuns ( runMap , runDates , cron ) ;
68
+ }
69
+ }
70
+
71
+ return runMap ;
72
+ }
73
+
74
+ private static void AddJobRuns ( IDictionary < DateTime , List < Type > > runMap , IEnumerable < DateTime > runDates , CronRegistryEntry cron )
75
+ {
76
+ foreach ( var runDate in runDates )
77
+ {
78
+ if ( runMap . TryGetValue ( runDate , out var value ) )
79
+ {
80
+ value . Add ( cron . Type ) ;
81
+ }
82
+ else
83
+ {
84
+ runMap [ runDate ] = new List < Type > { cron . Type } ;
85
+ }
86
+ }
87
+ }
88
+
89
+ private static DateTime UtcNowMinutePrecision ( )
90
+ {
91
+ var now = DateTime . UtcNow ;
92
+ return new DateTime ( now . Year , now . Month , now . Day , now . Hour , now . Minute , 0 ) ;
93
+ }
94
+ }
0 commit comments