Skip to content

A lightweight and efficient background task queue for ASP.NET Core, built using TPL Dataflow. It allows you to offload work from controllers and services to a background processing pipeline with controlled concurrency, dependency injection support, and graceful shutdown handling.

License

Notifications You must be signed in to change notification settings

sonicmouse/BackgroundTaskQueue.AspNetCore

Repository files navigation

BackgroundTaskQueue.AspNetCore

NuGet Version

A simple and scalable way to queue fire-and-forget background work in ASP.NET Core using dependency injection, scoped lifetimes, and category-based parallelism.

This service enables background work execution without requiring hosted services in your own code, while supporting graceful shutdown, cancellation, and error handling.

Features

  • Scoped dependency injection for background tasks
  • Optional named categories with configurable concurrency and queueing
  • Bounded capacity with task rejection
  • Graceful cancellation on shutdown
  • Customizable exception logging via IOffloadWorkExceptionLogger
  • Agressive unit testing

Installation

Register the service with default configuration:

// Defaults to a MaxDegreeOfParallelism of 3, unbounded capacity
builder.Services.AddOffloadWorkService();

Or customize the default configuration:

builder.Services.AddOffloadWorkService((OffloadWorkServiceOptions opt) =>
{
    // Controls how many tasks can run concurrently
    opt.MaxDegreeOfParallelism = 5;
    // Limits the number of active tasks (-1 for unlimited)
    opt.BoundedCapacity = 100;
});

Or define multiple named categories:

builder.Services.AddOffloadWorkService((OffloadWorkServiceCategoryOptions cat) =>
{
    cat.AddCategory("email", new OffloadWorkServiceOptions
    {
        MaxDegreeOfParallelism = 2,
        BoundedCapacity = 10
    });

    cat.AddCategory("pdf", new OffloadWorkServiceOptions
    {
        MaxDegreeOfParallelism = 4,
        BoundedCapacity = -1 // unbounded
    });
});

Usage

Inject IOffloadWorkService into your controller or service.

public sealed class MyController: ControllerBase
{
    private readonly IOffloadWorkService _offloader;

    public MyController(IOffloadWorkService offloadWorkService)
    {
        _offloader = offloadWorkService;
    }
}

Offload to the default category:

[HttpPost, Route("reindex")]
public IActionResult Reindex()
{
    var accepted = _offloader.Offload(async (sp, _, ct) =>
    {
        var search = sp.GetRequiredService<ISearchIndexer>();
        await search.ReindexAsync(ct);
    }, state: default);

    return accepted ? Accepted() :
        StatusCode(StatusCodes.Status429TooManyRequests, "Queue full");
}

Offload to a named category:

[HttpPost, Route("send-email")]
public IActionResult SendEmail([FromBody] EmailRequest request)
{
    var accepted = _offloader.Offload("email", async (sp, _, ct) =>
    {
        var sender = sp.GetRequiredService<IEmailSender>();
        await sender.SendAsync(request.To, request.Subject, request.Body, ct);
    }, state: default);

    return accepted ? Accepted() :
        StatusCode(StatusCodes.Status429TooManyRequests, "Email queue full");
}

Offload and avoid closures:

[HttpPost, Route("send-email")]
public IActionResult SendEmail([FromBody] EmailRequest request)
{
    var state = (request.To, request.Subject, request.Body);

    var accepted = _offloader.Offload("email", async static (sp, state, ct) =>
    {
        var sender = sp.GetRequiredService<IEmailSender>();
        await sender.SendAsync(state.To, state.Subject, state.Body, ct);
    }, state);

    return accepted ? Accepted() :
        StatusCode(StatusCodes.Status429TooManyRequests, "Email queue full");
}

Checking Queue Length

You can also retrieve the current length of the task queue for monitoring or logging purposes:

var queueLength = _offloader.GetActiveCount();
var queueLengthEmail = _offloader.GetActiveCount("email");

Custom Exception Logging

Implement a custom logger to capture exceptions from background tasks.

public sealed class MyExceptionLogger : IOffloadWorkExceptionLogger
{
    public void Log(Exception ex, string? category)
    {
        // Send to telemetry, logger, etc.
    }
}

Register it:

builder.Services.AddTransient<IOffloadWorkExceptionLogger, MyExceptionLogger>();

If registered, it will be used in place of the default ILogger<OffloadWorkService> for exceptions.

Behavior

  • BoundedCapacity is total active + queued items
  • Offload returns false if the queue is full
  • Background tasks receive a scoped IServiceProvider
  • Cancellation tokens are honored during shutdown
  • Logging is customizable but falls back to ILogger if needed

Clean Shutdown

The service cancels all running and queued tasks during shutdown using linked cancellation tokens. Resources are disposed automatically.

Contributing

Contributions are welcome! If you have improvements or bug fixes, please open an issue or submit a pull request.

License

This project is licensed under the MIT License.

About

A lightweight and efficient background task queue for ASP.NET Core, built using TPL Dataflow. It allows you to offload work from controllers and services to a background processing pipeline with controlled concurrency, dependency injection support, and graceful shutdown handling.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages