项目

后台工作器

简介

后台工作器是应用程序中在后台运行的简单独立线程。通常,它们会定期运行以执行某些任务。例如:

  • 后台工作器可以定期运行以删除旧日志
  • 后台工作器可以定期运行以确定非活跃用户发送邮件以吸引用户返回应用程序。

创建后台工作器

后台工作器应直接或间接实现IBackgroundWorker接口。

后台工作器本质上是单例模式。因此,工作器类只会实例化并运行一个实例。

BackgroundWorkerBase

BackgroundWorkerBase是创建后台工作器的一种简便方式。

public class MyWorker : BackgroundWorkerBase
{
    public override Task StartAsync(CancellationToken cancellationToken = default)
    {
        //...
    }

    public override Task StopAsync(CancellationToken cancellationToken = default)
    {
        //...
    }
}

StartAsync方法中启动工作器(应用程序启动时调用),在StopAsync方法中停止工作器(应用程序关闭时调用)。

您可以直接实现IBackgroundWorker,但BackgroundWorkerBase提供了一些有用的属性,如Logger

AsyncPeriodicBackgroundWorkerBase

假设我们希望将用户设为非活跃状态,如果用户在过去30天内未登录应用程序。AsyncPeriodicBackgroundWorkerBase类简化了创建定期工作器的过程,因此我们将在下面的示例中使用它:

如果您将使用Hangfire后台工作器管理器Quartz后台工作器管理器,可以使用CronExpression属性为后台工作器设置cron表达式。

public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
{
    public PassiveUserCheckerWorker(
            AbpAsyncTimer timer,
            IServiceScopeFactory serviceScopeFactory
        ) : base(
            timer, 
            serviceScopeFactory)
    {
        Timer.Period = 600000; //10分钟
        //CronExpression = "0 0/10 * * * ?"; //每10分钟运行一次,仅适用于Quartz或Hangfire集成。
    }

    protected async override Task DoWorkAsync(
        PeriodicBackgroundWorkerContext workerContext)
    {
        Logger.LogInformation("开始:设置非活跃用户状态...");

        //解析依赖项
        var userRepository = workerContext
            .ServiceProvider
            .GetRequiredService<IUserRepository>();

        //执行工作
        await userRepository.UpdateInactiveUserStatusesAsync();

        Logger.LogInformation("完成:设置非活跃用户状态...");
    }
}
  • AsyncPeriodicBackgroundWorkerBase使用AbpAsyncTimer(一个线程安全的计时器)对象来确定周期。我们可以在构造函数中设置其Period属性。
  • 需要实现DoWorkAsync方法来执行定期工作。
  • 最佳实践是从PeriodicBackgroundWorkerContext解析依赖项,而不是通过构造函数注入。因为AsyncPeriodicBackgroundWorkerBase使用一个IServiceScope,该作用域在工作完成后会被释放
  • AsyncPeriodicBackgroundWorkerBase捕获并记录DoWorkAsync方法抛出的异常。

注册后台工作器

创建后台工作器类后,应将其添加到IBackgroundWorkerManager中。最常见的位置是模块类的OnApplicationInitializationAsync方法:

[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
    public override async Task OnApplicationInitializationAsync(
        ApplicationInitializationContext context)
    {
        await context.AddBackgroundWorkerAsync<PassiveUserCheckerWorker>();
    }
}

context.AddBackgroundWorkerAsync(...)是以下表达式的快捷扩展方法:

await context.ServiceProvider
    .GetRequiredService<IBackgroundWorkerManager>()
    .AddAsync(
        context
            .ServiceProvider
            .GetRequiredService<PassiveUserCheckerWorker>()
    );

因此,它会解析给定的后台工作器并将其添加到IBackgroundWorkerManager中。

虽然我们通常在OnApplicationInitializationAsync中添加工作器,但对此没有限制。您可以在任何地方注入IBackgroundWorkerManager并在运行时添加工作器。后台工作器管理器将在应用程序关闭时停止并释放所有已注册的工作器。

选项

AbpBackgroundWorkerOptions类用于为后台工作器设置选项。目前只有一个选项:

  • IsEnabled(默认值:true):用于启用/禁用应用程序的后台工作器系统。

有关如何设置选项,请参阅选项文档。

确保应用程序始终运行

后台工作器仅在应用程序运行时工作。如果您将后台作业执行托管在Web应用程序中(这是默认行为),应确保您的Web应用程序配置为始终运行。否则,后台作业仅在应用程序使用时工作。

在集群中运行

如果在集群环境中同时运行应用程序的多个实例,请小心。在这种情况下,每个应用程序都会运行相同的工作器,如果工作器在同一资源上运行(例如处理相同的数据),可能会产生冲突。

如果这对您的工作器是个问题,您有以下选项:

  • 实现后台工作器,使其在集群环境中正常工作。使用分布式锁确保并发控制是一种方法。应用程序实例中的后台工作器可以处理分布式锁,因此其他应用程序实例中的工作器将等待锁。这样,只有一个工作器执行实际工作,而其他工作器处于空闲等待状态。如果实现这一点,您的工作器可以安全运行,无需关心应用程序的部署方式。
  • 在所有应用程序实例中停止后台工作器(将AbpBackgroundWorkerOptions.IsEnabled设置为false),只保留一个实例运行工作器。
  • 在所有应用程序实例中停止后台工作器(将AbpBackgroundWorkerOptions.IsEnabled设置为false),并创建一个专用的应用程序(可能是一个在自己的容器中运行的控制台应用程序,或一个在后台运行的Windows服务)来执行所有后台任务。如果您的后台工作器消耗大量系统资源(CPU、RAM或磁盘),这可能是一个不错的选择,因此您可以将该后台应用程序部署到专用服务器上,后台任务不会影响应用程序的性能。

为后台作业和工作器使用相同的存储

如果多个应用程序共享相同的后台作业和工作器存储(Default、Hangfire、RabbitMQ和Quartz),应配置提供程序选项以使用应用程序名称进行隔离。

默认后台作业/工作器

AbpBackgroundJobWorkerOptions中设置ApplicationName属性为您的应用程序名称:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
    {
        options.ApplicationName = context.Services.GetApplicationName()!;
    });
}

Hangfire后台作业/工作器

AbpHangfireOptions中设置DefaultQueuePrefix属性为您的应用程序名称:

public override void ConfigureServices(ServiceConfigurationContext context)
{
    Configure<AbpHangfireOptions>(options =>
    {
        options.DefaultQueuePrefix = context.Services.GetApplicationName()!;
    });
}

Quartz后台作业/工作器

设置quartz.scheduler.instanceName属性为您的应用程序名称:

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",
        };
    });
}

RabbitMQ后台作业

AbpRabbitMqBackgroundJobOptions中设置DefaultQueueNamePrefixDefaultDelayedQueueNamePrefix属性为您的应用程序名称:

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;
    });
}

集成

后台工作器系统是可扩展的,您可以用自己的实现或预构建的集成之一替换默认的后台工作器管理器。

查看预构建的工作器管理器替代方案:

另请参阅

在本文档中