• ABP官方文档翻译 7.1 后台Jobs和Workers


    后台Jobs和Workers

    介绍

      ABP提供了后台jobs和workers,他们用于在应用的后台线程中执行一些任务。

    后台Jobs

      后台Jobs用来排队一些需要在后台队列或持久化的方式执行一些任务。以下几种情况可以使用后台jobs:

    • 执行一些长时运行的任务而不需要用户等待。例如,用户按了一个'report'按钮开始一个长时运行的报表job。可以将这个job添加到队列,当执行完成的时候通过邮件发送给用户报表结果。
    • 创建一个重试且持久化的任务来保证一段代码的成功运行。例如:可以在后台job中发送邮件来克服临时错误并保证它最终被发送。这样,当发送邮件时,用户不必等待。

    关于Job持久化

      参见后台Job存储了解更多关于job持久化的信息。

    创建后台Job

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

      这是最简单的后台job:

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

      后台job定义了Excute方法,它有一个输入参数。参数类型为泛型类参数。

      后台job必须注册到依赖注入系统。实现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仓储(获得用户电子邮件)和电子邮件sender(发送邮件的服务)简单的发送邮件。SimpleSendEmailJobArgs为job的参数,定义如下:

    [Serializable]
    public class SimpleSendEmailJobArgs
    {
        public long SenderUserId { get; set; }
    
        public long TargetUserId { get; set; }
    
        public string Subject { get; set; }
    
        public string Body { get; set; }
    }

      job参数应该是可以序列化的,因为它被序列化并存储在数据库中。ABP默认后台job管理器使用JSON序列化(它不需要[Serilizable]特性),但是最好定义[Serializable]特性,因为我们可能在将来切换到其他job管理器,它可能使用.NET的内置二进制序列化。

      保持参数简单(如DTOs),不要包含实体或其他非序列化的对象。如在SimpleSendEmailJob示例中所示,我们可以存储实体的Id并从实体的仓储中获得实体。

    在队列中添加一个新Job

      定义后台job之后,我们可以注入并使用IBackgroundJobManager来添加job到队列。参见TestJob定义示例:

    public class MyService
    {
        private readonly IBackgroundJobManager _backgroundJobManager;
    
        public MyService(IBackgroundJobManager backgroundJobManager)
        {
            _backgroundJobManager = backgroundJobManager;
        }
    
        public void Test()
        {
            _backgroundJobManager.Enqueue<TestJob, int>(42);
        }
    }

      当入队时,我们使用42作为参数。IBackgroundJobManager将实例化并执行TestJob,参数为42。

      让我们添加一个上面定义的新的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
                });
        }
    }

      Enque(或EnqueueAsync)方法还有其他参数如prioritydelay

    默认的后台Job管理器

      IBackgroundJobManager默认由BackgroundJobManager实现。它可以被其他后台job提供者(参见hangfire集成)取代。BackgroundJobManager的一些参考信息:

    • 它是一个单线程的FIFO的简单job队列。它使用IBackgroundJobStore来持久化jobs(参见下部分)。
    • 在job成功运行(不抛出任何异常但是会记录)或超时前会一直重试。默认的重试时间为2天。
    • 当成功执行后,它从仓储(数据库)中删除job。如果它超时了,就设置为abandoned并保留在数据库中。
    • 每次重试前它会递增时间。第一次尝试等待1分钟,第二次等待2分钟,第三次等待4分钟...
    • 它以固定间隔轮训jobs仓储。按优先级(升序)、重试次数(升序)排列查询jobs。

    后台Job存储

      默认的BackgroundJobManager需要一个数据仓储来保存和获取jobs。如果你没实现IBackgroundJobStore那么它使用InMemoryBackgroundJobStore,它不在持久化的数据库中存储jobs。你可以简单的实现它来在数据库中存储jobs或者可以使用已经实现了它的module-zero

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

    配置

      你可以在模块的PreInitialize方法中使用Configuration.BackgroundJobs来配置后台job系统。

    禁用Job执行

      你可以禁用应用程序的后台job执行:

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

      几乎不需要这样做。但是,假如你的应用运行了多个实例并使用同样的数据库(在web farm中)。在这种情况下,每个应用将使用同样的数据库查询job并执行。这会导致同样job的多次执行及其他的问题。为了阻止这种情况,有两种选择:

    • 你可以只为应用程序的一个实例启用job执行。
    • 你可以为应用程序的所有实例禁用job执行,然后创建一个单独的执行后台jobs的应用程序(例如:windows service)。

    异常处理

      因为默认的后台管理器会自动重试失败的jobs,它处理(并记录)所有的异常。如果当异常发生时你想要得到通知,可以创建一个事件处理者来处理AbpHandledExceptionData。后台管理器触发这个事件,它有一个BackgroundJobException异常对象参数,这个参数包装了真正的异常(获取InnerException获取真正的异常)。

    Hangfire集成

      后台管理器设计为可以被其他后台job管理器代替。参见hangfire集成文档,使用Hangfire替换它。

    后台Workers

      后台Workers与后台Jobs不同。他们为在后台运行的简单独立的线程。通常,他们定期执行一些任务。例如:

    • 后台worker可以定期的删除老的日志。
    • 后台worker可以定期的判定不活动的用户并发送邮件来通知用户。

    创建后台Workers

      创建一个后台worker,我们需要实现IBackgroundWorker接口。作为另一个选择,我们可以基于我们的需求继承BackgroundWorkerBasePeriodicBackgroundWorkerBase

      假如我们想使用户失活,如果他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 moudle-zero中使用的真实代码。

    • 如果你从PeriodicBackgroundWokerBase(如在本例中)继承,需要实现DoWork方法来执行你的定期执行代码。
    • 如果你从BackgroundWorkerBase继承或直接实现IBackgroundWorker接口,你需要重写/实现Start,StopWaitToStop方法。Start和Stop方法需要为非阻塞的,WaitToStop方法需要等待worker完成它当前关键的job。

    注册后台Workers

      创建后台worker后,我们需要把它添加到IBackgroundWorkerManger。最常见的地方为模块的PostInitialize方法:

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

      我们通常在PostInitialize方法中添加worker,但并不限制。你可以在任何地方注入IBackgroundWorkerManager并添加worker。当应用程序关闭时,IBackgroundWorkerManager将停止并释放所有注册的worker。

    后台Worker生命周期

      后台workder一般实现为单例模式。但是并没有限制。如果你需要同个worker类的多个实例,你可以使它为瞬时的并添加多个实例到IBackgroundWorkerManager。在这种情况下,你的worker将为参数化的(也就是说你有一个单独的LogCleaner类但是两个LogCleaner worker实例他们监视并清理不同的日志文件夹)。

    高级计划安排

      ABP后台worker系统比较简单。除了上面展示的定期执行worker,它没有计划安排系统。如果你需要更多计划安排的特征,我们建议你使用Quartz或其他类库。

    让你的应用一直运行

      后台jobs和workers只有当你应用程序运行时才会工作。如果长时间web应用没有请求执行,ABP应用默认会关闭。所以,如果你在web应用中(这是默认行为)寄宿后台job的执行,你需要保证你的web应用配置为一直执行。否则,后台job只有应用在使用时才能工作。

      有一些技术可以实现。最简单的方式是使用外部应用定期请求你的web应用。这样,你可以检查文本应用是否已启动并运行。Hangfire文档解释了一些其他的方式。

    返回主目录

  • 相关阅读:
    2019-2020-1学期20192401《网络空间安全专业导论》第一周学习总结
    14201771010119穷吉
    13201771010119穷吉
    12穷吉201771010119
    11201771010119穷吉
    实验10穷吉201771010119
    实验九201771010119穷吉
    201771010119穷吉第八周
    七次201771010119穷吉
    实验六20177101010119穷吉
  • 原文地址:https://www.cnblogs.com/xajh/p/7118773.html
Copyright © 2020-2023  润新知