This repository contains the code for the Tingle.PeriodicTasks
libraries. This project exists to simplify the amount of work required to add periodic tasks to .NET projects. The existing libraries seem to have numerous complexities in setup especially when it comes to the use of framework concepts like dependency inject and options configuration. At Tingle Software, we use this for all our periodic tasks that is based on .NET. However, the other libraries have more features such as persistence and user interfaces which are not yet available here.
Package | Description |
---|---|
Tingle.PeriodicTasks |
Basic implementation of periodic tasks in .NET |
Tingle.PeriodicTasks.AspNetCore |
AspNetCore endpoints for managing periodic tasks. |
Tingle.PeriodicTasks.EventBus |
Support for triggering periodic tasks using events from Tingle.EventBus. |
Install the necessary library/libraries using Package Manager
Install-Package Tingle.PeriodicTasks
Install-Package Tingle.PeriodicTasks.AspNetCore
Install-Package Tingle.PeriodicTasks.EventBus
Install the necessary library/libraries using dotnet CLI
dotnet add Tingle.PeriodicTasks
dotnet add Tingle.PeriodicTasks.AspNetCore
dotnet add Tingle.PeriodicTasks.EventBus
Create a periodic task
class DatabaseCleanerTask : IPeriodicTask
{
private readonly ILogger logger;
public DatabaseCleanerTask(ILogger<DatabaseCleanerTask> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task ExecuteAsync(PeriodicTaskExecutionContext context, CancellationToken cancellationToken = default)
{
logger.LogInformation("Cleaned up old records from the database");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
Register the periodic task in your Program.cs
file:
services.AddPeriodicTasks(builder =>
{
builder.AddTask<DatabaseCleanerTask>(o => o.Schedule = "*/1 * * * *"); // every minute
});
To support running on multiple machines, distributed locking is used. See library for more information.
You need to register IDistributedLockProvider
this in your Program.cs
file which can be backed by multiple sources. For this case, we use file-based locks.
// register IDistributedLockProvider
services.AddSingleton<Medallion.Threading.IDistributedLockProvider>(provider =>
{
return new Medallion.Threading.FileSystem.FileDistributedSynchronizationProvider(Directory.CreateDirectory("distributed-locks"));
});
This core library (Tingle.PeriodicTasks
) is instrumented using System.Diagnostics.Activity
and System.Diagnostics.ActivitySource
.
This makes it easy to use with OpenTelemetry by listening to the Tingle.PeriodicTasks
activity source.
services.AddOpenTelemetry().WithTracing().AddSource("Tingle.PeriodicTasks");
You can choose to manage the periodic tasks in using endpoints in AspNetCore. Update your application setup as follows.
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
+ app.MapPeriodicTasks();
await app.RunAsync();
Remember to add authorization policies are needed. For example:
app.MapPeriodicTasks().RequireAuthorization("policy-name-here");
Endpoints available:
-
GET /registrations
: list the registered periodic tasks -
GET /registrations/{name}
: retrieve the registration of a given periodic task by name -
GET /registrations/{name}/history
: retrieve the execution history of a given periodic task -
POST /execute
: execute a periodic task{ "name": "DatabaseCleanerTask", // can also be DatabaseCleaner, databasecleaner "wait": true, // Whether to await execution to complete. "throw": true // Whether to throw an exception if one is encountered. }
Triggering via Tingle.EventBus
This helps trigger periodic tasks on demand or from another internal source without blocking. Update your EventBus setup as follows.
services.AddEventBus(builder =>
{
builder.AddInMemoryTransport();
+ builder.AddPeriodicTasksTrigger();
});
Publish events using the TriggerPeriodicTaskEvent type. In JSON:
{
"id": "...",
"event": {
"name": "DatabaseCleanerTask", // can also be DatabaseCleaner, databasecleaner
"wait": true, // Whether to await execution to complete.
"throw": true // Whether to throw an exception if one is encountered.
},
// omitted for brevity
}
By default, all registered periodic tasks are enabled. This means that they will always run as per schedule. In some scenarios you may want to disable them by default but execute them on demand via AspNetCore endpoints or via the EventBus.
To disable a periodic task on registration:
services.AddPeriodicTasks(builder =>
{
builder.AddTask<DatabaseCleanerTask>(o =>
{
+ o.Enable = false;
o.Schedule = "*/1 * * * *";
});
});
To disable a periodic task via IConfiguration
, update your appsettings.json
:
{
"PeriodicTasks": {
"Tasks": {
"DatabaseCleanerTask": { // Can use the FullName of the type
"Enable": false
}
}
}
}
To disable via environment variable:
Name | Value |
---|---|
PeriodicTasks__Tasks__DatabaseCleanerTask__Enable |
"false" |
In some cases, you want to run a periodic task instead of your application. This is supported so long as your app uses the IHost
pattern.
Update your configuration or environment variables to add:
Name/Key | Value |
---|---|
PERIODIC_TASK_NAME |
"DatabaseCleanerTask" |
Next, update your application startup:
- app.RunAsync();
+ app.RunOrExecutePeriodicTaskAsync();
A common practice is to build one application project for your AspNetCore application housing your periodic tasks, but you need to run the database cleanup task in a separate process (e.g. a Kubernetes CronJob or Azure Container App Job). Offloading longer running or greedier tasks to a separate process, or just for easier visibility on sensitive ones is a good thing.
For Azure Container App Job, your bicep file would like:
resource job 'Microsoft.App/jobs@2023-05-01' = {
name: '...'
properties: {
environmentId: appEnvironment.id
configuration: {
triggerType: 'Schedule'
scheduleTriggerConfig: {
cronExpression: '10 * * * *'
}
}
template: {
containers: [
{
image: '...'
name: containerName
env: [
{ name: 'PERIODIC_TASK_NAME', value: 'DatabaseCleanerTask' }
{ name: 'EventBus__DefaultTransportWaitStarted', value: 'false' } // Ensure EventBus is available
]
}
]
}
}
// omitted for brevity
}
- Simple sample setup
- Using IConfiguration to configure PeriodicTasks
- Managing periodic tasks in AspNetCore
- Triggering periodic tasks using Tingle.EventBus
- Save executions to a database using Entity Framework
- Add retries using Polly's Resilience Pipelines
Please leave all comments, bugs, requests, and issues on the Issues page. We'll respond to your request ASAP!
The Library is licensed under the MIT license. Refer to the LICENSE file for more information.