返回总目录


本篇目录

介绍

ABP提供了后台工作和后台工作者,它们会在应用程序的后台线程中执行一些任务。

后台工作

后台工作以队列和持续的方式在后台给一些即将被执行的任务排队。你可能因为某些原因需要后台工作,比如:

  • 执行长时间运行的任务。比如,一个用户按了“report”按钮来启动一个长时间运行的报告工作,点击了这个按钮你不可能让用户一直处于等待状态,所以你应该将这个工作(job)添加到 队列(queue)中,然后,当这项工作完成时,通过邮件将报告结果发送给该用户。
  • 创建重复尝试(re-trying)和持续的任务来保证代码将会 成功执行。比如,你可以在后台工作中发送邮件以克服 临时失败,并 保证邮件最终能够发送出去。因此,当发送邮件的时候用户不需要等待。

创建一个后台工作

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

下面是一个最简单的后台工作:

  1. public class TestJob : BackgroundJob<int>, ITransientDependency
  2. {
  3. public override void Execute(int number)
  4. {
  5. Logger.Debug(number.ToString());
  6. }
  7. }

该后台工作定义了一个需要输入参数的Execute方法。参数类型是泛型参数类型。

后台工作必须注册到依赖注入系统中,实现ITransientDependency是最简单的方式。

接下来定义一个更现实的工作,它会在后台队列中发送邮件:

  1. public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
  2. {
  3. private readonly IRepository<User, long> _userRepository;
  4. private readonly IEmailSender _emailSender;
  5. public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
  6. {
  7. _userRepository = userRepository;
  8. _emailSender = emailSender;
  9. }
  10. public override void Execute(SimpleSendEmailJobArgs args)
  11. {
  12. var senderUser = _userRepository.Get(args.SenderUserId);
  13. var targetUser = _userRepository.Get(args.TargetUserId);
  14. _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
  15. }
  16. }

我们注入了user仓储(为了获得用户信息)和email发送者(发送邮件的服务),然后简单地发送了该邮件。SimpleSendEmailJobArgs是该工作的参数,它定义如下:

  1. [Serializable]
  2. public class SimpleSendEmailJobArgs
  3. {
  4. public long SenderUserId { get; set; }
  5. public long TargetUserId { get; set; }
  6. public string Subject { get; set; }
  7. public string Body { get; set; }
  8. }

工作参数应该是serializable(可序列化),因为要将它 序列化并存储到数据库中。虽然ABP默认的后台工作管理者使用了JSON序列化(它不需要[Serializable]特性),但是最好定义 [Serializable]特性,因为我们将来可能会转换到其他使用二进制序列化的工作管理者。

记住,要保持你的参数简单,不要在参数中包含实体或者其他非可序列化的对象。正如上面的例子演示的那样,我们只存储了实体的 Id,然后在该工作的内部从仓储中获得该实体。

添加新工作到队列

当定义了一个后台工作后,我们就可以注入并使用IBackgroundJobManager来添加一个工作到队列中。看上面定义的TestJob的例子:

  1. public class MyService
  2. {
  3. private readonly IBackgroundJobManager _backgroundJobManager;
  4. public MyService(IBackgroundJobManager backgroundJobManager)
  5. {
  6. _backgroundJobManager = backgroundJobManager;
  7. }
  8. public void Test()
  9. {
  10. _backgroundJobManager.Enqueue<TestJob, int>(42);
  11. }
  12. }

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

让我们看一下如何为上面定义的SimpleSendEmailJob添加一个新的工作:

  1. [AbpAuthorize]
  2. public class MyEmailAppService : ApplicationService, IMyEmailAppService
  3. {
  4. private readonly IBackgroundJobManager _backgroundJobManager;
  5. public MyEmailAppService(IBackgroundJobManager backgroundJobManager)
  6. {
  7. _backgroundJobManager = backgroundJobManager;
  8. }
  9. public async Task SendEmail(SendEmailInput input)
  10. {
  11. await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
  12. new SimpleSendEmailJobArgs
  13. {
  14. Subject = input.Subject,
  15. Body = input.Body,
  16. SenderUserId = AbpSession.GetUserId(),
  17. TargetUserId = input.TargetUserId
  18. });
  19. }
  20. }

Enqueu (或 EnqueueAsync)方法还有其他的参数,比如 priority和 delay(优先级和延迟)

默认的后台工作管理者

IBackgroundJobManager默认是由BackgroundJobManager实现的。它可以被其他的后台工作提供者替代(看后面的Hangfire集成)。关于默认的BackgroundJobManager一些信息如下:

  • 它是一个在单线程中以FIFO(First In First Out)工作的简单工作队列,使用 IBackgroundJobStore来持久化工作。
  • 它会重复尝试执行工作,直到工作成功执行(不会抛出任何异常)或者超时。默认的超时是一个工作2天。
  • 当成功执行后,它会从存储(数据库)中删除该工作。如果超时了,就会将该工作设置为 abandoned(废弃的),并保留在数据库中。
  • 在重复尝试一个工作之间会增加等待时间。第一次重试时等待1分钟,第二次等待2分钟,第三次等待4分钟等等。
  • 在固定的时间间隔轮询工作的存储。查询工作时先按优先级排序,再按尝试次数排序。

后台工作存储

默认的BackgroundJobManager需要一个数据存储来保存、获得工作。如果你没有实现IBackgroundJobStore,那么它会使用 InMemoryBackgroundJobStore,它不会将工作持久化到数据库中。你可以简单地实现它来存储工作到数据库或者你可以使用module-zero,它已经实现了IBackgroundJobStore。

如果你正在使用第三方的工作管理者(像Hangfire),那么不需要实现IBackgroundJobStore。

配置

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

关闭工作执行功能

你可能想关闭应用程序的后台工作执行:

  1. public class MyProjectWebModule : AbpModule
  2. {
  3. public override void PreInitialize()
  4. {
  5. Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
  6. }
  7. //...
  8. }

这种情况很罕见,但是想一下,如果你正在对相同的数据库运行多个应用的实例(在web应用中)。在这种情况下,每个应用都会为工作查询相同的数据库并执行它们。这会导致相同工作的多次执行和一些其它问题。要阻止这种情况发生,你有两种选择:

  • 你可以只为该应用的一个实例开启工作执行。
  • 你可以为该web应用的所有实例关闭工作执行,然后创建一个会执行后台工作的独立应用(比如一个Windows服务)。

Hangfire集成

后台工作管理者设计成了可以被其他的后台工作管理者取代。查看Hangfire集成来替代默认的后台工作管理者。

后台工作者

后台工作者不同于后台工作。它们是运行在应用后台的简单独立线程。一般来说,它们会定期地执行一些任务。比如:

  • 后台工作者可以定期运行来删除旧的日志
  • 后台工作者可以定期运行来确定不活跃的用户,并给他们发送邮件以使他们返回你的应用。

创建后台工作者

要创建后台工作者,我们应该实现IBackgroundWorker接口。除此之外,我们可以基于需求从 BackgroundWorkerBase或者 PeriodicBackgroundWorkerBase继承。

假设一个用户在过去30天内没有登录到该应用,那我们想要让Ta的状态为passive。看下面的代码:

  1. public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
  2. {
  3. private readonly IRepository<User, long> _userRepository;
  4. public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository)
  5. : base(timer)
  6. {
  7. _userRepository = userRepository;
  8. Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)
  9. }
  10. [UnitOfWork]
  11. protected override void DoWork()
  12. {
  13. using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
  14. {
  15. var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));
  16. var inactiveUsers = _userRepository.GetAllList(u =>
  17. u.IsActive &&
  18. ((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null))
  19. );
  20. foreach (var inactiveUser in inactiveUsers)
  21. {
  22. inactiveUser.IsActive = false;
  23. Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");
  24. }
  25. CurrentUnitOfWork.SaveChanges();
  26. }
  27. }
  28. }

这是现实中的代码,并且在具有module-zero模块的ABP中直接有效。

  • 如果你从PeriodicBackgroundWorkerBase 类继承(如这个例子),那么你应该实现 DoWork方法来执行定期运行的代码。
  • 如果从BackgroundWorkerBase继承或直接实现了 IBackgroundWorker,那么你要重写或者实现Start, Stop 和 WaitToStop方法。Start和Stop方法应该是 非阻塞的(non-blocking),WaitToStop方法应该等待工作者完成当前重要的工作。

注册后台工作者

创建一个后台工作者后,我们应该把它添加到IBackgroundWorkerManager,通常放在模块的PostInitialize方法中:

  1. public class MyProjectWebModule : AbpModule
  2. {
  3. //...
  4. public override void PostInitialize()
  5. {
  6. var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
  7. workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
  8. }
  9. }

虽然一般我们将工作者添加到PostInitialize方法中,但是没有强制要求。你可以在任何地方注入IBackgroundWorkerManager,在运行时添加工作者。

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

后台工作者生命周期

后台工作者一般实现为单例的,但是没有严格限制。如果你需要相同工作者类的多个实例,那么可以使它成为transient(每次使用时创建),然后给IBackgroundWorkerManager添加多个实例。在这种情况下,你的工作者很可能会参数化(比如,你有单个LogCleaner类,但是两个LogCleaner工作者实例会监视并清除不同的log文件夹)。

让你的应用程序一直运行

只有当你的应用运行时,后台工作和工作者才会工作。如果一个Asp.Net 应用长时间没有执行请求,那么它默认会关闭(shutdown)。如果你想让后台工作一直在web应用中执行(这是默认行为),那么你要确保web应用配置成了总是运行。否则,后台工作只有在应用使用时才会执行。

有很多技术来实现这个目的。最简单的方法是从外部应用定期向你的web应用发送请求。这样,你可以检查web应用是否开启并且处于运行状态。Hangfire文档讲解了一些其他的方法。

ABP框架理论学习之后台工作(Jobs)和后台工作者(Workers)的更多相关文章

  1. 在一个shell中查看管理 任务(前台和后台)/工作jobs 的命令

    在一个shell中查看管理 任务(前台和后台)/工作jobs 的命令 jobs是在同一个shell环境而言, 才有意义的. 为什么有jobs这个命令? 是因为, 如果从cmd line运行gui程序时 ...

  2. C#高级知识点&(ABP框架理论学习高级篇)——白金版

    前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...

  3. ABP框架理论学习之Hangfire集成

    返回总目录 Hangfire是一个综合的后台工作管理者.你可以将Hangfire集成到ABP中,这样就可以不使用默认的后台工作管理者了.但你仍然可以为Hangfire使用相同的后台工作API.这样,你 ...

  4. ABP框架理论学习之Debugging

    返回总目录 所有的官方ABP nuget包都是支持GitLink的,这意味着你可以在项目中轻松地调试所有的以Abp为前缀的Nuget包. 要开启这项支持,"启用源服务器支持"选项应 ...

  5. ABP框架学习

    一.总体与公共结构 1,ABP配置 2,多租户 3,ABP Session 4,缓存 5,日志 6,设置管理 7,Timing 8,ABPMapper 9,发送电子邮件 二.领域层 10,实体 11, ...

  6. X-Admin&ABP框架开发-消息通知

    业务型网站使用过程中,消息通知是一个不可或缺的功能,采用站内通知.短信通知.邮件通知.微信通知等等各种方式都有,ABP框架对这部分工作已经封装的很好了,站在巨人的肩膀上,一览全貌,带来的就是心情舒畅. ...

  7. ABP源码分析九:后台工作任务

    文主要说明ABP中后台工作者模块(BackgroundWorker)的实现方式,和后台工作模块(BackgroundJob).ABP通过BackgroundWorkerManager来管理Backgr ...

  8. 解析ABP框架中的事务处理和工作单元,ABP事务处理

    通用连接和事务管理方法连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connec ...

  9. 后台工作者HangFire与ABP框架Abp.Hangfire及扩展

    HangFire与Quartz.NET相比主要是HangFire的内置提供集成化的控制台,方便后台查看及监控,对于大家来说,比较方便. HangFire是什么 Hangfire是一个开源框架(.NET ...

随机推荐

  1. ***IT程序员自我修养和情商提升文章

    低情商的13个表现 --------------------------------------------------------------------- — THE END —

  2. Aspx页面模拟WebService功能

    在后台引入 using System.Web.Services 命名空间 然后在编写web服务方法: [WebMethod] public static string GetData(string t ...

  3. mysql salve从库设置read only 属性

    在MySQL数据库中,在进行数据迁移和从库只读状态设置时,都会涉及到只读状态和Master-slave的设置和关系.     经过实际测试,对于MySQL单实例数据库和master库,如果需要设置为只 ...

  4. 用WinForm写的员工考勤项目!!!!!!

    先说几句,作为一个还在学习的程序员,掌握的知识有限:但我利用自身所学,给一些像我一样还在学习的码农提供我的绵薄之力! 写的不好,但是尽力了,希望大牛指点.多多吐槽!!! 好了开始说项目需求: 实现新增 ...

  5. Chrome 中的彩蛋,一款小游戏,你知道吗?

    今天看到一篇文章,介绍chrome中的彩蛋,带着好奇心进去看了一眼,没想到发现了一款小游戏,个人觉得还不错,偶尔可以玩一下,放松放松心情!^_^ 当 Chrome 无法连接到互联网时, 或者上着网突然 ...

  6. 命名规范(数据库,c#)

    Ⅰ.  Naming Conventions 1. Table Naming Rule 1a ( Prefix) 新加的Table要加上適當的前缀 e.g.  mUsr, eTxn, tmpRolle ...

  7. [资料分享]ACCESS2013 零基础到精通

    Microsoft Office Access是由微软发布的关系数据库管理系统.它结合了 MicrosoftJet Database Engine 和 图形用户界面两项特点,是 Microsoft O ...

  8. 模块化管理工具兼打包工具 webpack

    webpack 是一个[模块化管理工具]兼[打包工具] 是一个工具(和seajs,requirejs管理前端模块的方式是不一样) 在webpack一个文件就是一个模块! seajs,requirejs ...

  9. 初识linux

    1.版本 稳定版本:偶数版如2.6.X 发展中的版本:奇数版如2.5.X linux distribution包含:linux kernel + free software + documentati ...

  10. php和ajax 服务器端做轮询推送(定义)

    基于HTTP的长连接,是一种通过长轮询方式实现"服务器推"的技术,它弥补了HTTP简单的请求应答模式的不足,极大地增强了程序的实时性和交互性. 一.什么是长连接.长轮询? 用通俗易 ...