ABP文档 - 后台作业和工作者

文档目录

 

本节内容:

 

简介

ABP提供了后台作业和工作者,用来在后台线程里执行应用里的某些任务。

 

后台作业

后台作业用一种队列且持久稳固的方式安排一些待执行后台任务,你可能有几个理由,需要用到后台作业,例如:

  • 为执行长时间运行的任务而用户无需等待,例如:用户按了一下“报告”按钮开始一个长时间运行的报告任务,你把这个任务添加到队列里,它任务完成后,通过电子邮件发送报告结果给你。
  • 为创建可重试且持久稳固的任务来保证一个代码将会被完成运行,例如:你可以在后台作业里发送一个电子邮件,为克服临时的失败且保证它最终将会被发送,当然当发送电子邮件时用户也无需等待。

关于作业持久化

查看后台作业存储小节获取有关作业持久化的更多信息。

 

创建一个后台作业

我们可以通过继承BackgroundJob<TArgs>类或直接实现IBackgroundJob<TArgs>接口来创建一个后台作业。

下列为一个非常简单的后台作业:

public class TestJob : BackgroundJob<int>, ITransientDependency
{
    public override void Execute(int number)
    {
        Logger.Debug(number.ToString());
    }
}

一个后台作业定义了一个Execute方法,接受一个输入参数,参数类型就是定义泛型类的参数,如上例所示。

一个后台作业必须注册到依赖注入,实现ITransientDependency是最简单的方式。

让我们定义一个更真实的作业:在后台队列里发送电子邮件:

public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
{
    private readonly IRepository<User, long> _userRepository;
    private readonly IEmailSender _emailSender;

    public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
    {
        _userRepository = userRepository;
        _emailSender = emailSender;
    }

    public override void Execute(SimpleSendEmailJobArgs args)
    {
        var senderUser = _userRepository.Get(args.SenderUserId);
        var targetUser = _userRepository.Get(args.TargetUserId);

        _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
    }
}

我们注入user仓储(可获取用户电子邮件)和邮件发送器(一个发送邮件的服务),并简单的发送邮件,SimpleSendEmailJobArgs是作业的参数,它的定义如下所示:

[Serializable]
public class SimpleSendEmailJobArgs
{
    public long SenderUserId { get; set; }

    public long TargetUserId { get; set; }

    public string Subject { get; set; }

    public string Body { get; set; }
}

一个作业的参数应当可序列化,因为它要被序列化后存储到数据库,虽然ABP默认后台作业管理器使用JSOn序列化器(它不需要使用[Serializable]特性),更好还是定义为[Serializable],因为将来我们可能会替换成另一个作业管理器,可能会使用.net内置的二进制序列化器。

保存你的参数简单(如DTO),不要包含实体或其它非序列化对象,如所示的SimpleSendEmailJob,我们可以只存储一个实体的Id,并通过它从作业内部的仓储里获取实体。

 

在队列里添加一个新作业

在定义完一个后台作业之后,我们可以注入并使用IBackgroundJobManager给队列添加一个作业,看一下使用上面已定义的TestJob的例子:

public class MyService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

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

    public void Test()
    {
        _backgroundJobManager.Enqueue<TestJob, int>(42);
    }
}

当加入队列时我们发送参数42, IBackgroundManager将实体化并以42为TestJob的参数执行它。

让我们看一下使用上面定义的SimpleSendEmailJob的例子:

[AbpAuthorize]
public class MyEmailAppService : ApplicationService, IMyEmailAppService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

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

    public async Task SendEmail(SendEmailInput input)
    {
            await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
            new SimpleSendEmailJobArgs
            {
                Subject = input.Subject,
                Body = input.Body,
                SenderUserId = AbpSession.GetUserId(),
                TargetUserId = input.TargetUserId
            });
    }
}

Enqueue(或EnqueueAsync) 有其它参数,如priority和delay。

 

默认的后台作业管理器

BackgroundJobManager默认实现了IBackgroundJobManager,它可被其它后台作业提供器替代(查看Hangfire文档)。如下为一些关于默认BackgroudJobManager的信息:

  • 它是一个简单的作业队列,以FIFO(先进先出)方式单线程作业,它使用IBackgroundJobStore来持久化作业(见下一小节)。
  • 它一直重试作业执行直到作业成功运行(只记录日志不抛出异常)或超时,默认超时为一个作业2天。
  • 在作业成功运行后,它从存储(数据库)里删除这个作业,如果超时了,就把这个作业设置为“被抛弃的”,然后离开数据库。
  • 它在重试一个作业之间递增等待时间,第一次重试,等待1分钟,第二次重试,等待2分钟,第三次重试,等待4分钟,如此类推。
  • 它在固定的间隔里给作业的存储投票,查询作业按优先级(升序)排序,然后按尝试次数(升序)排。

 

后台作业存储

默认的BackgroundJobManager需要一个数据存储来保存和获取作业,如果你没有实现IBackgroundJobStore,它会使用InMemoryBackgroundJobStore,它不在持久化的数据库中保存作业,你可以简单的实现这个接口,让作业存储到一个数据库或使用已经实现该接口的module-zero

如果你使用第三方的作业管理器(如Hangfire),不需要实现IBackgroundJobStore。

 

配置

你可以在模块的PreInitialize方法里,使用Configuration.BackgroundJobs配置你的后台作业系统。

 

禁用作业执行

你可能会想为你的应用禁用后台作业执行:

public class MyProjectWebModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
    }

    //...
}

很少需要这样,但考虑一下你正在运行一个应用的多个实例并访问同一个数据库,这种情况下,每个应用将向同个数据库查询作业并执行它们,这可能导致同个作业的多次执行和一些其它问题,为阻止这种情况,我们有两个选择:

  • 你可以只允许应用的一个实例来完成作业的执行。
  • 你可以禁用应用的所有实例执行作业,再单独创建一个应用(如:一个windows服务)来执行后台作业。

 

Hangfire 集成 

后台作业管理器设计成可被其它后台管理器所替换,查看Hangfire集成文档如何用Hangfire代替。 

 

后台工作者

后台工作者与后台作业不同,它简单的依赖应用在后台运行的线程,通常地,它定期执行一些任务,例如:

  • 一个后台工作者可以定期删除旧日志。
  • 一个后台工作者可以定期检测不活跃的用户,然后发邮件给他们,让他们重新使用你的应用。

 

创建一个后台工作者

为创建一个后台工作者,我们应当实现IBackgroundWorker接口,我们还可以选择直接从BackgroundWorkerBase或PeriodicBackgroundWorkerBase基类上继承。

假设我们想把超过30天未登录的用户设置为“消极”的,代码如下:

public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
{
    private readonly IRepository<User, long> _userRepository;

    public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository)
        : base(timer)
    {
        _userRepository = userRepository;
        Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)
    }

    [UnitOfWork]
    protected override void DoWork()
    {
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));

            var inactiveUsers = _userRepository.GetAllList(u =>
                u.IsActive &&
                ((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null))
                );

            foreach (var inactiveUser in inactiveUsers)
            {
                inactiveUser.IsActive = false;
                Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");
            }

            CurrentUnitOfWork.SaveChanges();
        }
    }
}

 

这是一段真实的代码,它工作在ABP的module-zero里。

  • 如果你从PeriodicBackgroundWorkerBase继承(如这个例子),需要实现DoWork方法来执行你的定期工作。
  • 如果你从BackgroundWorkerBase继承或直接实现IBackgroundWorker,需要重写/实现Start、Stop和WaitToStop方法,Start和Stop方法应当是非阻塞的,WaitToStop方法需要等待工作者完成它当前的工作。

 

注册后台工作者

在完成创建后台工作者后,需要把它添加到IBackgroundWorkerManager,非常通用的地方是:你模块的PostInitialize方法里:

public class MyProjectWebModule : AbpModule
{
    //...

    public override void PostInitialize()
    {
        var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
        workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
    }
}

 

虽然我们通常在PostInitialize里添加工作者,但不是一定要这样,你可以在任何地方注入IBackgroundWorkerManager,然后在运行时添加工作者。

当你应用关闭时,IBackgroundWorkerManager将停止并释放所有已注册的工作者。

 

后台工作者生存方式

后台工作者以单例模式被创建,但也不是一定要这样,如果你需要同个工作者类的多个实例,你可以使它是“暂时的”并添加多个实例到IBackgroundWorkermanager,在这种情况下,你的工作者可能需要参数(假设你有一个单独的LogCleaner类,但有两个LogCleaner工作者实例用来监视和删除不同的日志目录)。

 

使你的应用一直运行

只有当你的应用运行时,后台作业和工作者才能工作,如果一个Web应用长时间没有收到访问请求,它默认地会被关闭,所以,如果你的宿主后台作业运行在你的web应用里(这是默认行为),你应当确保你的web应用被配置成一直运行,否则,只有当你的应用在使用的时候,后台作业才能工作。

这里有些技术可以做到这点,一个非常简单的办法是:从一个外部应用里定期访问你的Web应用,从而你可以一直检查你的web应用是否一直运行着。Hangfire文档解释了一些其它方法。

 

kid1412附:英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Background-Jobs-And-Workers

posted @ 2016-11-02 10:37 kid1412 阅读(...) 评论(...) 编辑 收藏