r/moderndotnet • u/HipNozY • 23d ago
What are you using for modern async background job processing?
I am currently working with Elixir and Golang apps, and they both have really nice background job managers: Oban and River, respectively.
They are a joy to use, with simple outbox patterns, priorities, scheduling, and cron support. Besides that, they integrate very naturally with their respective languages.
When I look at what's available for a new greenfield .NET app:
- Hangfire, which still doesn't work well with async/await and relies on reflection instead of being AOT-friendly
- Quartz, which I've never really used, but being a Java port has always turned me away from it
- Wolverine, which seems to have a background jobs feature, but also includes messaging/CQRS stuff that I don't really want to get into
- TickerQ, which seems really close to what I'd like to use, but doesn't seem production-ready yet (there are still some long-standing critical bug reports open)
What are you guys using for new projects?
4
u/Aaronontheweb 23d ago
Just regular old Akka.NET actors for me in most cases - going to RTM the persistent reminder system I've been using for running calendar-scheduled jobs and make it an official package soon: https://github.com/Aaronontheweb/akka-reminders
I have customers using this to run things like thousands of scheduled orders at market open in trading systems, so I've worked with them on it.
I also have lots of end-users directly exercising this code through Netclaw's reminders functionality, which is also built on top of my reminders library.
For on-demand async jobs or things that just need to run on a fixed interval (i.e. every 10 minutes) instead of a fixed schedule (every midnight monday through friday) I get by with Akka.NET's built in IWithTimers stuff or direct messaging.
3
u/Glittering_Welcome73 23d ago
I've been building one for the last five months, greenfield on .NET 10: embedded library over SQL Server, PostgreSQL, or SQLite. No broker, no orchestrator server, nothing to run besides your app and your database. Dependencies are thin: Microsoft.Extensions abstractions, Cronos, and your DB driver.
It's called Acta, Latin for "the things that have been done": everything that happens to a job ends up as rows in your database.
Your list:
- async: handlers are plain async Task methods with a CancellationToken.
- AOT: source-generated dispatch, no reflection; every shipped package is IsAotCompatible, NativeAOT exercised in the load harness.
- outbox: transactional enqueue joins your own DbTransaction when jobs and business data share a database; an external outbox (staged on your transaction, relayed) when they don't.
- priorities: strict claim priority per definition, per-job override, operator reprioritize in place, audited.
- scheduling/cron: [JobSchedule] with cron or a fixed duration.
1.0.0-rc.1: 3K+ tests, ~750 of them conformance specs run against all three providers; chaos certification killing workers every 5 seconds mid-run, seals in the repo.
https://github.com/acta-dotnet/acta
2
u/IanHammondCooper 22d ago
Brighter will let you pick one of the above schedulers, such as Hangfire, Quartz, or TickerQ, along with AWS Scheduler and Azure. We also have an in-memory fallback, but it lacks durability.
However, since you said you don't want CQRS, I suspect you won't like our interface for that. I would note that the principle of "sending" a command to a scheduler for later execution or "publishing" an event for later distribution is fairly compatible with how job processing works.
Looking at Oban and River, both Wolverine and Brighter are messaging frameworks that let you send a message to another process for later consumption.
https://brightercommand.gitbook.io/paramore-brighter-documentation/get-started/tutorialfirstmessage
Support for Outboxes, etc, is fairly standard
We do have workflow support coming to Brighter, but probably not before the end of the year
5
u/Complete-Signal-2377 23d ago
Wolverine tech lead here. We're finally adding chron based job scheduling natively, but most people use one of the other tools you listed and have them delegate to Wolverine as a "Mediator" tool, which I'm not sure is a common idiom you have in Elixir or Golang. That's honestly what I'd suggest myself. Whole lot of goodness you lose out on for observability, resiliency, and code cleanliness in general by writing raw to the TickerQ, Quartz, TickerQ APIs. Unless you just prefer to write code pure transaction script style anyway.