diff --git a/AsyncScheduler/IShutdownJob.cs b/AsyncScheduler/IShutdownJob.cs
new file mode 100644
index 0000000..9749482
--- /dev/null
+++ b/AsyncScheduler/IShutdownJob.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+
+namespace AsyncScheduler
+{
+ ///
+ /// Interface which can be implemented by a Job,
+ /// so its shutdown method is executed, when scheduler is stopped by CancellationToken.
+ ///
+ public interface IShutdownJob : IJob
+ {
+
+ ///
+ /// Method is executed on shutdown of Scheduler/Job independent whether this job was started before or not.
+ /// It is allowed to throw an exception as they are caught.
+ ///
+ /// message for Log
+ Task Shutdown();
+ }
+}
\ No newline at end of file
diff --git a/AsyncScheduler/Scheduler.cs b/AsyncScheduler/Scheduler.cs
index f0ca03c..5672f68 100644
--- a/AsyncScheduler/Scheduler.cs
+++ b/AsyncScheduler/Scheduler.cs
@@ -161,6 +161,55 @@ public async Task Start(CancellationToken cancellationToken)
}
}
}
+
+ await ExecuteShutdownMethods();
+ }
+
+ private async Task ExecuteShutdownMethods()
+ {
+ var shutdownTasksWithErrorHandler = JobManager.Jobs
+ .Where(j => typeof(IShutdownJob).IsAssignableFrom(j.Value))
+ .Select(pair => new Tuple>(pair.Key, ExecuteShutdownJob(pair)))
+ .Select(t => t.Item2.ContinueWith(task => HandleShutdownFinished(t.Item1, task)));
+
+ await Task.WhenAll(shutdownTasksWithErrorHandler);
+ }
+
+ private async Task HandleShutdownFinished(string jobKey, Task shutdownTask)
+ {
+ try
+ {
+ var result = await shutdownTask;
+ _logger.LogInformation("Shutdown job for {jobKey} finished with result: {result}", jobKey, result);
+ }
+ catch (Exception e)
+ {
+ _logger.LogWarning(e, "Unable to shutdown job {jobKey}", jobKey);
+ }
+ }
+
+ private async Task ExecuteShutdownJob(KeyValuePair job)
+ {
+ using (_logger.BeginScope("job", job.Key))
+ {
+ var jobInstance = CreateJobInstance(job);
+ if (jobInstance == null)
+ {
+ _logger.LogWarning("Cannot create service! Skipping job {jobKey}", job.Key);
+ return "Job cannot be initialized";
+ }
+
+ try
+ {
+ var shutdown = ((IShutdownJob) jobInstance).Shutdown();
+ return await shutdown;
+ }
+ catch (Exception e)
+ {
+ _logger.LogWarning(e, "Unable to shutdown job {jobKey}", job.Key);
+ return "Shutdown of job not executed";
+ }
+ }
}
private void ExecuteQuickStarts(CancellationToken cancellationToken)
diff --git a/AsyncSchedulerTest/ShutdownJobTest.cs b/AsyncSchedulerTest/ShutdownJobTest.cs
new file mode 100644
index 0000000..d177b8c
--- /dev/null
+++ b/AsyncSchedulerTest/ShutdownJobTest.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using AsyncScheduler;
+using AsyncScheduler.JobStorage;
+using AsyncScheduler.Schedules;
+using AsyncSchedulerTest.TestData;
+using AsyncSchedulerTest.TestUtils;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AsyncSchedulerTest
+{
+ public class ShutdownJobTest
+ {
+ private readonly Scheduler _scheduler;
+ private readonly ShutdownJob _shutdownJobInstancee;
+
+ public ShutdownJobTest(ITestOutputHelper testOutputHelper)
+ {
+ _shutdownJobInstancee = new ShutdownJob();
+ var serviceProvider = new TestActivator(null, _shutdownJobInstancee);
+ var jobManager = new JobManager(serviceProvider, new XUnitLogger(testOutputHelper),
+ new InMemoryStorage());
+ _scheduler = new Scheduler(serviceProvider, new XUnitLogger(testOutputHelper),
+ new UtcSchedulerClock(), jobManager);
+ }
+
+ [Fact]
+ public async Task TestShutdown()
+ {
+ _scheduler.JobManager.AddJob();
+ await RunScheduler(TimeSpan.FromSeconds(1));
+ _shutdownJobInstancee.ShutdownCount.Should().Be(1);
+ }
+
+ private async Task RunScheduler(TimeSpan schedulerTime)
+ {
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+ var schedulerTask = _scheduler.Start(cancellationTokenSource.Token);
+ // ReSharper disable MethodSupportsCancellation
+ await Task.Delay(schedulerTime).ContinueWith((t) => cancellationTokenSource.Cancel());
+ var schedulerFinishTimeout = TimeSpan.FromSeconds(1);
+ await Task.WhenAny(schedulerTask, Task.Delay(schedulerFinishTimeout));
+ cancellationTokenSource.Cancel();
+ await schedulerTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsyncSchedulerTest/TestData/ShutdownJob.cs b/AsyncSchedulerTest/TestData/ShutdownJob.cs
new file mode 100644
index 0000000..27b5336
--- /dev/null
+++ b/AsyncSchedulerTest/TestData/ShutdownJob.cs
@@ -0,0 +1,22 @@
+using System.Threading;
+using System.Threading.Tasks;
+using AsyncScheduler;
+
+namespace AsyncSchedulerTest.TestData
+{
+ public class ShutdownJob : IShutdownJob
+ {
+ public int ShutdownCount { get; private set; } = 0;
+
+ public async Task