A Job Scheduler sitting on top of IHostedService
in dotnet.
Often times one finds themself between the simplicity of the BackgroundService
/IHostedService
and the complexity of
a full blown Hangfire
or Quartz
scheduler.
This library aims to fill that gap by providing a simple and easy to use job scheduler that can be used in any dotnet
application and feels "native".
So no need for setting up a database, just schedule your stuff right away! The library gives you two ways of scheduling jobs:
- Instant jobs - just run a job right away
- Cron jobs - schedule a job using a cron expression
- The ability to schedule jobs using a cron expression
- The ability to instantly run a job
- Parameterized jobs - instant as well as cron jobs!
- Integrated in ASP.NET - Access your DI container like you would in any other service
- Get notified when a job is done (either successfully or with an error).
As this is a simple scheduler, some features are not included by design. If you need these features, you might want to
look into a more advanced scheduler like Hangfire
or Quartz
.
- Job persistence - Jobs are not persisted between restarts of the application.
- Job history - There is no history of jobs that have been run.
- Retries - If a job fails, it is not retried.
- Progress state - There is no way to track the progress of a job. The library will support notifying when a job is done, but not the progress of the job itself.
- The job scheduler always uses UTC time. We might change that in the future.
- Import the namespace (or let your IDE do the dirty work)
using LinkDotNet.NCronJob;
- Create a job
public class PrintHelloWorld : IJob
{
private readonly ILogger<PrintHelloWorld> logger;
public PrintHelloWorld(ILogger<PrintHelloWorld> logger)
{
this.logger = logger;
}
public Task RunAsync(JobExecutionContext context, CancellationToken token)
{
logger.LogInformation("Hello World");
logger.LogInformation("Parameter: {Parameter}", context.Parameter);
return Task.CompletedTask;
}
}
- Register the NCronJob and the job in your
Program.cs
builder.Services.AddNCronJob();
builder.Services.AddCronJob<PrintHelloWorld>(options =>
{
// Every minute
options.CronExpression = "* * * * *";
// Optional parameter
options.Parameter = "Hello World";
});
- Run your application and see the magic happen
If the need arises and you want to trigger a job instantly, you can do so:
public class MyService
{
private readonly IInstantJobRegistry jobRegistry;
public MyService(IInstantJobRegistry jobRegistry) => this.jobRegistry = jobRegistry;
public void MyMethod() => jobRegistry.AddInstantJob<MyJob>("I am an optional parameter");
}
NCronJob provides a way to get notified when a job is done. For this, implement a IJobNotificationHandler<TJob>
and register it in your DI container.
Services.AddNotificationHandler<MyJobNotificationHandler, MyJob>();
This allows to run logic after a job is done. The JobExecutionContext
and the Exception
(if there was one) are
passed to the Handle
method.
public class MyJobNotificationHandler : IJobNotificationHandler<MyJob>
{
private readonly ILogger<MyJobNotificationHandler> logger;
public MyJobNotificationHandler(ILogger<MyJobNotificationHandler> logger)
{
this.logger = logger;
}
public Task HandleAsync(JobExecutionContext context, Exception? exception, CancellationToken token)
{
if (exception is not null)
{
logger.LogError(exception, "Job failed");
}
else
{
logger.LogInformation("Job was successful");
logger.LogInformation("Output: {Output}", context.Output);
}
return Task.CompletedTask;
}
}
By default, the jobs are run in the same scope as the scheduler. If there are long-running jobs that are synchronous,
this might block the scheduler from running other jobs. To avoid this, you can set the IsolationLevel
.
Services.AddCronJob<LongRunningJob>(options =>
{
options.CronExpression = "* * * * *";
options.IsolationLevel = IsolationLevel.Task;
});
You can achieve the same by using await Task.Yield()
at the beginning your job, but this is a more explicit way of
doing it.
The NCronJob scheduler can be configured to log at a specific log level.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"LinkDotNet.NCronJob": "Debug"
Thanks to all contributors and people that are creating bug-reports and valuable input: