LinkDotNet.NCronJob
2.1.0-preview
The repository and package moved to a new organization and name. Use NCronJob instead!
Read more about this in our announcement: https://github.com/NCronJob-Dev/NCronJob/discussions/66
See the version list below for details.
dotnet add package LinkDotNet.NCronJob --version 2.1.0-preview
NuGet\Install-Package LinkDotNet.NCronJob -Version 2.1.0-preview
<PackageReference Include="LinkDotNet.NCronJob" Version="2.1.0-preview" />
paket add LinkDotNet.NCronJob --version 2.1.0-preview
#r "nuget: LinkDotNet.NCronJob, 2.1.0-preview"
// Install LinkDotNet.NCronJob as a Cake Addin #addin nuget:?package=LinkDotNet.NCronJob&version=2.1.0-preview&prerelease // Install LinkDotNet.NCronJob as a Cake Tool #tool nuget:?package=LinkDotNet.NCronJob&version=2.1.0-preview&prerelease
<h1 align="center">NCronJob</h1>
<p align="center"> <img src="assets/logo_small.png" alt="logo" width="120px" height="120px"/> <br> <em>Scheduling made easy</em> <br> </p>
NCronJob
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
Features
- 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).
- Retries - If a job fails, it will be retried.
Not features
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.
- 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.
Short example
- 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(options =>
options.AddJob<PrintHelloWorld>(j =>
{
// Every minute and optional parameter
j.WithCronExpression("* * * * *")
.WithParameter("Hello World");
}));
- Run your application and see the magic happen
Triggering an instant job
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.RunInstantJob<MyJob>("I am an optional parameter");
}
Getting notified when a job is done
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.
builder.Services.AddNCronJob(options =>
options.AddCronJob<PrintHelloWorld>(j =>
{
// Every minute and optional parameter
j.WithCronExpression("* * * * *")
.WithParameter("Hello World");
})
.AddNotificationHandler<MyJobNotificationHandler, PrintHelloWorld>());
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;
}
}
Advanced Cases
Scheduling multiple schedules for the same job
If you want to schedule a job multiple times, you can do so by calling utilizing the builder:
Services.AddNCronJob(options =>
options.AddJob<PrintHelloWorld>(j =>
{
j.WithCronExpression("* * * * *")
.WithParameter("Hello World")
.And
.WithCronExpression("0 * * * *")
.WithParameter("Hello World Again");
}));
Log Level
The NCronJob scheduler can be configured to log at a specific log level.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"LinkDotNet.NCronJob": "Debug"
Migration from v1
to v2
Version 2 of NCronJob brings some breaking changes to mae a better API.
CronExpression
moved towards builder
- In
v1
one would define as such:
services.AddNCronJob();
services.AddCronJob<PrintHelloWorld>(options =>
{
options.CronExpression = "* * * * *";
options.Parameter = "Hello World";
});
With v2
the CronExpression
is moved towards the builder pattern and AddCronJob
is merged into AddNCronJob
:
Services.AddNCronJob(options =>
{
options.AddJob<PrintHelloWorld>(j =>
{
j.WithCronExpression("* * * * *")
.WithParameter("Hello World");
});
});
This allows to easily define multiple jobs without adding much boilerplate code.
Services.AddNCronJob(options =>
{
options.AddJob<PrintHelloWorld>(p => p
.WithCronExpression("0 * * * *").WithParameter("Foo")
.And
.WithCronExpression("0 0 * * *").WithParameter("Bar"));
});
Retry Support
The new Retry support provides a robust mechanism for handling transient failures by retrying failed operations. This feature is implemented using the RetryPolicy
attribute that can be applied to any class implementing the IJob
interface.
How It Works
The RetryPolicy
attribute allows you to specify the number of retry attempts and the strategy for handling retries. There are two built-in retry strategies:
- ExponentialBackoff: Increases the delay between retry attempts exponentially.
- FixedInterval: Keeps the delay between retry attempts consistent.
Using Retry Policies
Here are examples of how to use the built-in retry policies:
Example 1: Basic Retry Policy, defaults to Exponential Backoff
[RetryPolicy(retryCount: 4)]
public class RetryJob(ILogger<RetryJob> logger) : IJob
{
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
{
var attemptCount = context.Attempts;
if (attemptCount <= 3)
{
logger.LogWarning("RetryJob simulating failure.");
throw new InvalidOperationException("Simulated operation failure in RetryJob.");
}
logger.LogInformation($"RetryJob with Id {context.Id} was attempted {attemptCount} times.");
await Task.CompletedTask;
}
}
Example 2: Fixed Interval
[RetryPolicy(4, PolicyType.FixedInterval)]
public class FixedIntervalRetryJob(ILogger<FixedIntervalRetryJob> logger) : IJob
{
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
{
var attemptCount = context.Attempts;
if (attemptCount <= 3)
{
logger.LogWarning("FixedIntervalRetryJob simulating failure.");
throw new InvalidOperationException("Simulated operation failure in FixedIntervalRetryJob.");
}
logger.LogInformation($"FixedIntervalRetryJob with Id {context.Id} was attempted {attemptCount} times.");
await Task.CompletedTask;
}
}
Advanced: Custom Retry Policies
You can also create custom retry policies by implementing the IPolicyCreator
interface. This allows you to define complex retry logic tailored to your specific needs.
[RetryPolicy<MyCustomPolicyCreator>(retryCount:4, delayFactor:1)]
public class CustomPolicyJob(ILogger<CustomPolicyJob> logger) : IJob
{
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
{
var attemptCount = context.Attempts;
if (attemptCount <= 3)
{
logger.LogWarning("FixedIntervalRetryJob simulating failure.");
throw new InvalidOperationException("Simulated operation failure in FixedIntervalRetryJob.");
}
logger.LogInformation($"CustomPolicyJob with Id {context.Id} was attempted {attemptCount} times.");
await Task.CompletedTask;
}
}
public class MyCustomPolicyCreator : IPolicyCreator
{
public IAsyncPolicy CreatePolicy(int maxRetryAttempts = 3, double delayFactor = 2)
{
return Policy.Handle<Exception>()
.WaitAndRetryAsync(maxRetryAttempts,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(delayFactor, retryAttempt)));
}
}
Concurrency Support
Concurrency support allows multiple instances of the same job type to run simultaneously, controlled by the SupportsConcurrency
attribute. This feature is crucial for efficiently managing jobs that are capable of running in parallel without interference.
How It Works
The SupportsConcurrency
attribute specifies the maximum degree of parallelism for job instances. This means you can define how many instances of a particular job can run concurrently, optimizing performance and resource utilization based on the nature of the job and the system capabilities.
Using the SupportsConcurrency Attribute
Here is an example of how to apply this attribute to a job:
Example: Concurrency in Jobs
[SupportsConcurrency(10)]
public class ConcurrentJob : IJob
{
private readonly ILogger<ConcurrentJob> logger;
public ConcurrentJob(ILogger<ConcurrentJob> logger)
{
this.logger = logger;
}
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
{
logger.LogInformation($"ConcurrentJob with Id {context.Id} is running.");
// Simulate some work by delaying
await Task.Delay(5000, token);
logger.LogInformation($"ConcurrentJob with Id {context.Id} has completed.");
}
}
Important Considerations
Ensuring Job Idempotency
When using concurrency, it's essential to ensure that each job instance is idempotent. This means that even if the job is executed multiple times concurrently or sequentially, the outcome and side effects should remain consistent, without unintended duplication or conflict.
Resource Allocation Caution
Jobs that are marked to support concurrency should be designed carefully to avoid contention over shared resources. This includes, but is not limited to, database connections, file handles, or any external systems. In scenarios where shared resources are unavoidable, proper synchronization mechanisms or concurrency control techniques, such as semaphores, mutexes, or transactional control, should be implemented to prevent race conditions and ensure data integrity.
Support & Contributing
Thanks to all contributors and people that are creating bug-reports and valuable input:
<a href="https://github.com/linkdotnet/NCronJob/graphs/contributors"> <img src="https://contrib.rocks/image?repo=linkdotnet/NCronJob" alt="Supporters" /> </a>
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. |
-
net8.0
- Microsoft.Extensions.Hosting (>= 8.0.0)
- NCrontab.Signed (>= 3.3.3)
- Polly (>= 8.3.1)
-
net9.0
- Microsoft.Extensions.Hosting (>= 8.0.0)
- NCrontab.Signed (>= 3.3.3)
- Polly (>= 8.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
2.4.6 | 538 | 5/21/2024 | |
2.4.5 | 334 | 5/20/2024 | |
2.4.4 | 115 | 5/20/2024 | |
2.4.3-preview | 99 | 5/20/2024 | |
2.4.1-preview | 97 | 5/18/2024 | |
2.4.0-preview | 114 | 5/8/2024 | |
2.3.2 | 240 | 5/8/2024 | |
2.3.1 | 129 | 5/7/2024 | |
2.3.0-preview | 122 | 5/7/2024 | |
2.2.1 | 141 | 5/5/2024 | |
2.2.0-preview | 128 | 5/4/2024 | |
2.1.4 | 152 | 5/1/2024 | |
2.1.3-preview | 110 | 4/30/2024 | |
2.1.2-preview | 109 | 4/28/2024 | |
2.1.1-preview | 107 | 4/27/2024 | |
2.1.0-preview | 109 | 4/25/2024 | |
2.0.5 | 292 | 4/19/2024 | |
2.0.4 | 153 | 4/16/2024 | |
2.0.3 | 136 | 4/15/2024 | |
2.0.2-preview | 107 | 4/15/2024 | |
2.0.1-preview | 111 | 4/15/2024 | |
2.0.0-preview | 138 | 4/14/2024 | |
1.0.2 | 249 | 4/11/2024 | |
1.0.1-preview | 120 | 4/10/2024 | |
1.0.0-preview | 101 | 4/10/2024 | |
0.13.2 | 157 | 3/29/2024 | |
0.13.1 | 196 | 3/25/2024 | |
0.13.0 | 251 | 3/23/2024 | |
0.12.0 | 160 | 3/22/2024 | |
0.11.5 | 139 | 3/22/2024 | |
0.11.4 | 138 | 3/21/2024 | |
0.11.3 | 125 | 3/21/2024 | |
0.11.2-preview | 107 | 3/21/2024 | |
0.11.1-preview | 102 | 3/21/2024 | |
0.11.0-preview | 114 | 3/21/2024 | |
0.10.1 | 147 | 3/19/2024 | |
0.10.0 | 151 | 3/18/2024 | |
0.9.3 | 132 | 3/18/2024 | |
0.9.2 | 139 | 3/17/2024 | |
0.9.1 | 139 | 3/17/2024 |
Changes in NCronJob
See the full changelog at https://github.com/linkdotnet/NCronJob/releases