Skip to content

Background Jobs

Background jobs are a part of pretty much every real world application.
I always start with Hangfire and in process background jobs and in most of the projects it stays that way. Hangfire handles retries, errors, storage, etc. It also comes with a free dashboard as you can see on their website.

Libraries & Tools

Example

Hangfire is set up with persistent storage on our postgres database.

Hangfire executes jobs on a background thread WITHIN your aspnetcore api.

By far the two most used types of jobs are FireAndForget and Recurring jobs.

jobs.Enqueue(() => SendAccountConfirmationEmail(9)) is a fire and forget job.
It returns immediately and sends the email in a background thread, however long it takes.

jobsAddOrUpdate("invalidate-invoices", () => InvalidateInvoices(), Cron.Daily);
public async Task InvalidateInvoices()
{
var offers = await context.Offers
.Where(x => x.CreatedAt <= DateTime.UtcNow.AddDays(-14))
.Where(x => !x.Accepted)
.Where(x => x.IsValid)
.ToListAsync();
foreach (var offer in offers)
offer.IsValid = false;
await context.SaveChangesAsync();
}

This checks all our offers daily and invalidates the ones that are older than 2 weeks.

Final Thoughts

Hangfire is so simple, its easy to abuse without thinking about it.

The most important thing to keep in mind is that hangfire stores the jobs in persistent storage.
This is great and the reason you use background jobs at all, because you don’t have to worry about losing long running, expensive jobs on exceptions or server restarts.

But it also means you cannot rely on any application state. A job should always be self contained.

Another thing is method arguments. The easiest way to avoid all gotchas is keep this in mind:
Hangfire serializes and then deserializes all method arguments.

So putting anything stateful in there, will not work.
A DbContext or a SqlConnection or even just any kind of class you build, that keeps internal state, will not work.

All your “job methods” should kinda look like DoExpensiveThing(5, "Mirco", 91).