Background Jobs

Introduction

Background jobs are used to queue some tasks to be executed in the background. You may need background jobs for several reasons. Here are some examples:

  • To perform long-running tasks without having the users wait. For example, a user presses a 'report' button to start a long-running reporting job. You add this job to the queue and send the report's result to your user via email when it's completed.
  • To create re-trying and persistent tasks to guarantee that a code will be successfully executed. For example, you can send emails in a background job to overcome temporary failures and guarantee that it eventually will be sent. That way users do not wait while sending emails.

Background jobs are persistent that means they will be re-tried and executed later even if your application crashes.

Abstraction Package

ABP provides an abstraction module and several implementations for background jobs. It has a built-in/default implementation as well as Hangfire, RabbitMQ and Quartz integrations.

Volo.Abp.BackgroundJobs.Abstractions NuGet package provides needed services to create background jobs and queue background job items. If your module only depend on this package, it can be independent from the actual implementation/integration.

Volo.Abp.BackgroundJobs.Abstractions package is installed to the startup templates by default.

Create a Background Job

A background job is a class that implements the IBackgroundJob<TArgs> interface or derives from the BackgroundJob<TArgs> class. TArgs is a simple plain C# class to store the job data.

This example is used to send emails in background. First, define a class to store arguments of the background job:

namespace MyProject
{
    public class EmailSendingArgs
    {
        public string EmailAddress { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }
    }
}

Then create a background job class that uses an EmailSendingArgs object to send an email:

using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;

namespace MyProject
{
    public class EmailSendingJob
        : AsyncBackgroundJob<EmailSendingArgs>, ITransientDependency
    {
        private readonly IEmailSender _emailSender;

        public EmailSendingJob(IEmailSender emailSender)
        {
            _emailSender = emailSender;
        }

        public override async Task ExecuteAsync(EmailSendingArgs args)
        {
            await _emailSender.SendAsync(
                args.EmailAddress,
                args.Subject,
                args.Body
            );
        }
    }
}

This job simply uses IEmailSender to send emails (see email sending document).

AsyncBackgroundJob is used to create a job needs to perform async calls. You can inherit from BackgroundJob<TJob> and override the Execute method if the method doesn't need to perform any async call.

Exception Handling

A background job should not hide exceptions. If it throws an exception, the background job is automatically re-tried after a calculated waiting time. Hide exceptions only if you don't want to re-run the background job for the current argument.

Cancelling Background Jobs

If your background task is cancellable, then you can use the standard Cancellation Token system to obtain a CancellationToken to cancel your job when requested. See the following example that uses the ICancellationTokenProvider to obtain the cancellation token:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;

namespace MyProject
{
    public class LongRunningJob : AsyncBackgroundJob<LongRunningJobArgs>, ITransientDependency
    {
        private readonly ICancellationTokenProvider _cancellationTokenProvider;

        public LongRunningJob(ICancellationTokenProvider cancellationTokenProvider)
        {
            _cancellationTokenProvider = cancellationTokenProvider;
        }

        public override async Task ExecuteAsync(LongRunningJobArgs args)
        {
            foreach (var id in args.Ids)
            {
                _cancellationTokenProvider.Token.ThrowIfCancellationRequested();
                await ProcessAsync(id); // code omitted for brevity
            }
        }
    }
}

A cancellation operation might be needed if the application is shutting down and we don't want to block the application in the background job. This example throws an exception if the cancellation is requested. So, the job will be retried the next time the application starts. If you don't want that, just return from the ExecuteAsync method without throwing any exception (you can simply check the _cancellationTokenProvider.Token.IsCancellationRequested property).

Job Name

Each background job has a name. Job names are used in several places. For example, RabbitMQ provider uses job names to determine the RabbitMQ Queue names.

Job name is determined by the job argument type. For the EmailSendingArgs example above, the job name is MyProject.EmailSendingArgs (full name, including the namespace). You can use the BackgroundJobName attribute to set a different job name.

Example

using Volo.Abp.BackgroundJobs;

namespace MyProject
{
    [BackgroundJobName("emails")]
    public class EmailSendingArgs
    {
        public string EmailAddress { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }
    }
}

Queue a Job Item

Now, you can queue an email sending job using the IBackgroundJobManager service:

public class RegistrationService : ApplicationService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public RegistrationService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task RegisterAsync(string userName, string emailAddress, string password)
    {
        //TODO: Create new user in the database...

        await _backgroundJobManager.EnqueueAsync(
            new EmailSendingArgs
            {
                EmailAddress = emailAddress,
                Subject = "You've successfully registered!",
                Body = "..."
            }
        );
    }
}

Just injected IBackgroundJobManager service and used its EnqueueAsync method to add a new job to the queue.

Enqueue method gets some optional arguments to control the background job:

  • priority is used to control priority of the job item. It gets an BackgroundJobPriority enum which has Low, BelowNormal, Normal (default), AboveNormal and Hight fields.
  • delay is used to wait a while (TimeSpan) before first try.

Disable Job Execution

You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process.

Use AbpBackgroundJobOptions to configure the job execution:

[DependsOn(typeof(AbpBackgroundJobsModule))]
public class MyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBackgroundJobOptions>(options =>
        {
            options.IsJobExecutionEnabled = false; //Disables job execution
        });
    }
}

Default Background Job Manager

ABP framework includes a simple IBackgroundJobManager implementation that;

  • Works as FIFO in a single thread.
  • Retries job execution until the job successfully runs or timeouts. Default timeout is 2 days for a job. Logs all exceptions.
  • Deletes a job from the store (database) when it's successfully executed. If it's timed out, it sets it as abandoned and leaves it in the database.
  • Increasingly waits between retries for a job. It waits 1 minute for the first retry, 2 minutes for the second retry, 4 minutes for the third retry and so on.
  • Polls the store for jobs in fixed intervals. It queries jobs, ordering by priority (asc) and then by try count (asc).

Volo.Abp.BackgroundJobs nuget package contains the default background job manager and it is installed to the startup templates by default.

Configuration

Use AbpBackgroundJobWorkerOptions in your module class to configure the default background job manager. The example below changes the timeout duration for background jobs:

[DependsOn(typeof(AbpBackgroundJobsModule))]
public class MyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBackgroundJobWorkerOptions>(options =>
        {
            options.DefaultTimeout = 864000; //10 days (as seconds)
        });
    }
}

Data Store

The default background job manager needs a data store to save and read jobs. It defines IBackgroundJobStore as an abstraction to store the jobs.

Background Jobs module implements IBackgroundJobStore using various data access providers. See its own documentation. If you don't want to use this module, you should implement the IBackgroundJobStore interface yourself.

Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice.

Clustered Deployment

The default background job manager is compatible with clustered environments (where multiple instances of your application run concurrently). It uses a distributed lock to ensure that the jobs are executed only in a single application instance at a time.

However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the distributed lock document to configure a provider for your application, if it is not already configured.

If you don't want to use a distributed lock provider, you may go with the following options:

  • Stop the background job manager (set AbpBackgroundJobOptions.IsJobExecutionEnabled to false as explained in the Disable Job Execution section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs).
  • Stop the background job manager (set AbpBackgroundJobOptions.IsJobExecutionEnabled to false as explained in the Disable Job Execution section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance.

Integrations

Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations.

See pre-built job manager alternatives:

See Also

Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.

In this document
Community Talks

Building Modular Monolith Applications Using .NET and ABP Framework

17 Oct, 17:00
Online
Watch the Event
Mastering ABP Framework Book
Mastering ABP Framework

This book will help you gain a complete understanding of the framework and modern web application development techniques.

Learn More