.Net Background Tasks
Posted on Mon 31 January 2022 in dotnet
Background Tasks
Running a background task
Running a task for your API, product or service used to involve creating a complex system with lots of custom code, even effectively a product in its own right just to trigger the code you want to run! This got a little easier with the addition of services like Hangfire, it gives you a nice admin page, it's well documented and commonly used. It's relatively easy to implement as well, though it is (I would argue) only worth adding to larger projects.
TaskRunner
In a previous job, they solved the problem of background tasks via a very complex system, but it was all that was available at the time. They had a SQL Server database called Tasks with a table also called Tasks which had fields such as
- AssemblyName
- Class name
- procedure name
- parameters
- schedule (daily/hourly/adhoc)
Another table called TaskRequests could be populated with adhoc runs of processes. They had a pool of servers (called runners) with a primary server which would read continuously from these tables and check with the other servers in the pool if they were free to have a task assigned to them.
Visually the Architecture looked like this,
This system largely worked quite well, allowed for easy scaling of job processing with the only real pain point being load on the SQL database. Which could be considerable, however it was offset by the sheer power of those machines, they had 512GB of ram in 2014!
IHostedService
Both of the larger task runner service and hangfire are for larger more intensive requirements, what if you just want a simple background job for your API or web service? Recently at a client, they had a need to gather the status of multiple API's for monitoring purposes, originally I had designed the system so that each API was being polled by a service called the Registry, the registry would store a list of services which had registered with it, when it's triggered, it would call out to the status endpoint associated with each service that had registered itself.
This works quite nicely but it has its limitations, mainly that each service has an endpoint waiting to be called which returns data. The additional function app which runs on a timer trigger is also an unnecessary extra resource to maintain. A better solution is to use make use of IHostedService, it would allow each service to have a background task which runs on a timer to make a REST call directly rather than waiting for it's status endpoint to be called. Turns the status update conceptually from a get to a push.
public class MyBackGroundService : IHostedService, IDisposable
{
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(60));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var response = await client.PostAsJsonAsync(...);
response.EnsureSuccessStatusCode();
}
}
Don't forget to add the service to your startup class!
services.AddHostedService<MyBackGroundService>();
As you can see, for simple background tasks, this is a much lighter and simpler way of getting the task done.