BackgroundQueue provides a fast, controlled way to execute background work in .NET applications.
It prevents overload by queueing and processing work asynchronously with configurable limits and built-in tracking.
- Supports both
TaskandValueTask - Configurable queue size
- Tracks running and pending work
- Simple DI registration
- Hosted service for automatic background processing
dotnet add package Soenneker.Utils.BackgroundQueueRegister the queue:
void ConfigureServices(IServiceCollection services)
{
services.AddBackgroundQueueAsSingleton();
}await serviceProvider.WarmupAndStartBackgroundQueue(cancellationToken);Synchronous start:
serviceProvider.WarmupAndStartBackgroundQueueSync(cancellationToken);await serviceProvider.StopBackgroundQueue(cancellationToken);Synchronous stop:
serviceProvider.StopBackgroundQueueSync(cancellationToken);{
"Background": {
"QueueLength": 5000,
"LockCounts": false,
"Log": false
}
}QueueLength– Maximum number of queued itemsLockCounts– Enables thread-safe tracking of running workLog– Enables debug logging
Inject IBackgroundQueue:
IBackgroundQueue _queue;
void MyClass(IBackgroundQueue queue)
{
_queue = queue;
}await _queue.QueueValueTask(_ => someValueTask(), cancellationToken);await _queue.QueueTask(_ => someTask(), cancellationToken);Avoid capturing variables in lambdas when queueing work. Captured lambdas allocate and can impact performance under load.
await _queue.QueueTask(ct => DoWorkAsync(id, ct));If id is a local variable, this creates a closure.
Use the stateful overloads with static lambdas.
await _queue.QueueValueTask(
myService,
static (svc, ct) => svc.ProcessAsync(ct),
ct);await _queue.QueueTask(
(logger, id),
static (s, ct) => s.logger.RunAsync(s.id, ct),
ct);Why this is better:
- No closure allocations
- Lower GC pressure
- Best performance for high-throughput queues
The non-stateful overloads remain available for convenience, but stateful queueing is recommended for hot paths.
await queue.WaitUntilEmpty(cancellationToken);Check if work is still processing:
bool isProcessing = await queueInformationUtil.IsProcessing(cancellationToken);Get current counts:
var (taskCount, valueTaskCount) =
await queueInformationUtil.GetCountsOfProcessing(cancellationToken);