docs/en/framework/infrastructure/background-workers/index.md
//[doc-seo]
{
"Description": "Learn how to implement and manage background workers in your application using the ABP Framework for efficient task automation."
}
Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples;
A background worker should directly or indirectly implement the IBackgroundWorker interface.
A background worker is inherently singleton. So, only a single instance of your worker class is instantiated and run.
BackgroundWorkerBase is an easy way to create a background worker.
public class MyWorker : BackgroundWorkerBase
{
public override Task StartAsync(CancellationToken cancellationToken = default)
{
//...
}
public override Task StopAsync(CancellationToken cancellationToken = default)
{
//...
}
}
Start your worker in the StartAsync (which is called when the application begins) and stop in the StopAsync (which is called when the application shuts down).
You can directly implement the
IBackgroundWorker, butBackgroundWorkerBaseprovides some useful properties likeLogger.
Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. AsyncPeriodicBackgroundWorkerBase class simplifies to create periodic workers, so we will use it for the example below:
You can use
CronExpressionproperty to set the cron expression for the background worker if you will use the Hangfire Background Worker Manager, Quartz Background Worker Manager, or TickerQ Background Worker Manager.
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
{
public PassiveUserCheckerWorker(
AbpAsyncTimer timer,
IServiceScopeFactory serviceScopeFactory
) : base(
timer,
serviceScopeFactory)
{
Timer.Period = 600000; //10 minutes
//CronExpression = "0 0/10 * * * ?"; //Run every 10 minutes, Only for Quartz or Hangfire integration.
}
protected async override Task DoWorkAsync(
PeriodicBackgroundWorkerContext workerContext)
{
Logger.LogInformation("Starting: Setting status of inactive users...");
//Resolve dependencies
var userRepository = workerContext
.ServiceProvider
.GetRequiredService<IUserRepository>();
//Do the work
await userRepository.UpdateInactiveUserStatusesAsync();
Logger.LogInformation("Completed: Setting status of inactive users...");
}
}
AsyncPeriodicBackgroundWorkerBase uses the AbpAsyncTimer (a thread-safe timer) object to determine the period. We can set its Period property in the constructor.DoWorkAsync method to execute the periodic work.PeriodicBackgroundWorkerContext instead of constructor injection. Because AsyncPeriodicBackgroundWorkerBase uses a IServiceScope that is disposed when your work finishes.AsyncPeriodicBackgroundWorkerBase catches and logs exceptions thrown by the DoWorkAsync method.After creating a background worker class, you should add it to the IBackgroundWorkerManager. The most common place is the OnApplicationInitializationAsync method of your module class:
[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
await context.AddBackgroundWorkerAsync<PassiveUserCheckerWorker>();
}
}
context.AddBackgroundWorkerAsync(...) is a shortcut extension method for the expression below:
await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.AddAsync(
context
.ServiceProvider
.GetRequiredService<PassiveUserCheckerWorker>()
);
So, it resolves the given background worker and adds to the IBackgroundWorkerManager.
While we generally add workers in OnApplicationInitializationAsync, there are no restrictions on that. You can inject IBackgroundWorkerManager anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down.
You can add a runtime worker without pre-defining a dedicated worker class. Inject IDynamicBackgroundWorkerManager and pass a handler directly:
public class MyModule : AbpModule
{
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
var dynamicWorkerManager = context.ServiceProvider
.GetRequiredService<IDynamicBackgroundWorkerManager>();
await dynamicWorkerManager.AddAsync(
"InventorySyncWorker",
new DynamicBackgroundWorkerSchedule
{
Period = 30000 //30 seconds
//CronExpression = "*/30 * * * *" //Every 30 minutes. Only for Hangfire or Quartz integration.
},
async (workerContext, cancellationToken) =>
{
var inventorySyncAppService = workerContext
.ServiceProvider
.GetRequiredService<IInventorySyncAppService>();
await inventorySyncAppService.SyncAsync(cancellationToken);
}
);
}
}
You can also remove a dynamic worker or update its schedule at runtime:
//Remove a dynamic worker
var removed = await dynamicWorkerManager.RemoveAsync("InventorySyncWorker");
//Update the schedule of a dynamic worker
var updated = await dynamicWorkerManager.UpdateScheduleAsync(
"InventorySyncWorker",
new DynamicBackgroundWorkerSchedule
{
Period = 60000 //Change to 60 seconds
}
);
IDynamicBackgroundWorkerManager is a separate interface from IBackgroundWorkerManager, dedicated to runtime (non-type-safe) worker management.workerName is the runtime identifier of the dynamic worker. If a worker with the same name already exists, it will be replaced.handler receives a DynamicBackgroundWorkerExecutionContext containing the worker name and a scoped IServiceProvider. It is a good practice to resolve dependencies from the workerContext.ServiceProvider instead of constructor injection.Period or CronExpression must be set in DynamicBackgroundWorkerSchedule.CronExpression is only supported by scheduler-backed providers (Hangfire, Quartz). The default in-memory provider requires Period and does not support CronExpression alone.FrozenDictionary for function registration, which requires all functions to be registered before the application starts.RemoveAsync stops and removes a dynamic worker. Returns true if the worker was found and removed. The exact semantics are provider-dependent — for persistent providers (Hangfire, Quartz), the persistent scheduling record is always cleaned up, but the return value may only reflect the in-memory registry state.UpdateScheduleAsync changes the schedule of an existing dynamic worker. The handler itself is not changed. Returns true if the schedule was updated. The exact semantics are provider-dependent — for persistent providers (Hangfire, Quartz), this also works correctly after an application restart, updating the persistent scheduling record even if the handler is no longer registered in memory.Important: Dynamic worker handlers are stored in memory only and are not persisted across application restarts. When using a persistent scheduler provider (Hangfire or Quartz), the recurring job entries remain in the database after a restart, but the handlers will no longer be registered. Until the handler is re-registered, each scheduled execution will be skipped with a warning log. To ensure handlers are always available, register them in
OnApplicationInitializationAsyncso they are re-registered on every startup.
AbpBackgroundWorkerOptions class is used to set options for the background workers. Currently, there is only one option:
IsEnabled (default: true): Used to enable/disable the background worker system for your application.See the Options document to learn how to set options.
Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use.
Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example).
If that's a problem for your workers, you have the following options:
AbpBackgroundWorkerOptions.IsEnabled to false) in all application instances except one of them, so only the single instance runs the workers.AbpBackgroundWorkerOptions.IsEnabled to false) 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 tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance.If multiple applications share the same storage for background jobs and workers (Default, Hangfire, RabbitMQ, and Quartz), you should configure the provider options to use the application name for isolation.
Set ApplicationName property in AbpBackgroundJobWorkerOptions to your application's name:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});
}
Set DefaultQueuePrefix property in AbpHangfireOptions to your application's name:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpHangfireOptions>(options =>
{
options.DefaultQueuePrefix = context.Services.GetApplicationName()!;
});
}
Set quartz.scheduler.instanceName property to your application's name:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzOptions>(options =>
{
options.Properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = context.Services.GetApplicationName(),
["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.serializer.type"] = "json",
["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Default"),
["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
};
});
}
Set DefaultQueueNamePrefix and DefaultDelayedQueueNamePrefix properties in AbpRabbitMqBackgroundJobOptions to your application's name:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpRabbitMqBackgroundJobOptions>(options =>
{
options.DefaultQueueNamePrefix = context.Services.GetApplicationName()!.EnsureEndsWith('.') + options.DefaultQueueNamePrefix;
options.DefaultDelayedQueueNamePrefix = context.Services.GetApplicationName()!.EnsureEndsWith('.') + options.DefaultDelayedQueueNamePrefix;
});
}
Background worker system is extensible and you can change the default background worker manager with your own implementation or on of the pre-built integrations.
See pre-built worker manager alternatives: