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, 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 = ; //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()); 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
ABP文档 - 后台作业和工作者的更多相关文章
- ABP文档笔记系列
ABP文档笔记 - 模块系统 及 配置中心 ABP文档笔记 - 事件BUS ABP文档笔记 - 数据过滤 ABP文档笔记 - 规约 ABP文档笔记 - 配置.设置.版本.功能.权限 ABP文档笔记 - ...
- ABP文档 - 通知系统
文档目录 本节内容: 简介 发送模式 通知类型 通知数据 通知重要性 关于通知持久化 订阅通知 发布通知 用户通知管理器 实时通知 客户端 通知存储 通知定义 简介 通知用来告知用户系统里特定的事件发 ...
- ABP文档 - Hangfire 集成
文档目录 本节内容: 简介 集成 Hangfire 面板授权 简介 Hangfire是一个综合的后台作业管理器,可以在ABP里集成它替代默认的后台作业管理器,你可以为Hangfire使用相同的后台作业 ...
- ABP文档 - Quartz 集成
文档目录 本节内容: 简介 安装 创建工作 调度工作 更多 简介 Quartz 是一个功能完整的开源工作调度系统,可用于最小的应用到一个大型的企业系统.Abp.Quartz 包简单地把Quartz集成 ...
- ABP文档笔记 - 通知
基础概念 两种通知发送方式 直接发送给目标用户 用户订阅某类通知,发送这类通知时直接分发给它们. 两种通知类型 一般通知:任意的通知类型 "如果一个用户发送一个好友请求,那么通知我" ...
- ABP文档 - 目录
ABP框架 概览 介绍 多层结构 模块系统 启动配置 多租户 集成OWIN 共同结构 依赖注入 会话 缓存 日志 设置管理 时间 领域层 实体 值对象(新) 仓储 领域服务 工作单元 领域事件(Eve ...
- ABP文档 - Javascript Api - AJAX
本节内容: AJAX操作相关问题 ABP的方式 AJAX 返回信息 处理错误 HTTP 状态码 WrapResult和DontWrapResult特性 Asp.net Mvc 控制器 Asp.net ...
- ABP文档 - EntityFramework 集成
文档目录 本节内容: Nuget 包 DbContext 仓储 默认仓储 自定义仓储 特定的仓储基类 自定义仓储示例 仓储最佳实践 ABP可使用任何ORM框架,它已经内置了EntityFrame(以下 ...
- ABP文档 - SignalR 集成
文档目录 本节内容: 简介 安装 服务端 客户端 连接确立 内置功能 通知 在线客户端 帕斯卡 vs 骆峰式 你的SignalR代码 简介 使用Abp.Web.SignalR nuget包,使基于应用 ...
随机推荐
- 读书笔记:JavaScript DOM 编程艺术(第二版)
读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...
- [APUE]标准IO库(上)
一.流和FILE对象 系统IO都是针对文件描述符,当打开一个文件时,即返回一个文件描述符,然后用该文件描述符来进行下面的操作,而对于标准IO库,它们的操作则是围绕流(stream)进行的. 当打开一个 ...
- Oracle Database 12c Data Redaction介绍
什么是Data Redaction Data Redaction是Oracle Database 12c的高级安全选项之中的一个新功能,Oracle中国在介绍这个功能的时候,翻译为“数据编纂”,在EM ...
- DDD 领域驱动设计-商品建模之路
最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念: 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- 用游标实现查询当前服务器所有数据库所有表的SQL
declare @name varchar(100) DECLARE My_Cursor CURSOR --定义游标 FOR (SELECT Name FROM Master..SysDatabase ...
- 缓存、队列(Memcached、redis、RabbitMQ)
本章内容: Memcached 简介.安装.使用 Python 操作 Memcached 天生支持集群 redis 简介.安装.使用.实例 Python 操作 Redis String.Hash.Li ...
- Entity Framework 教程——Entity Framework中的实体类型
Entity Framework中的实体类型 : 在之前的章节中我们介绍过从已有的数据库中创建EDM,它包含数据库中每个表所对应的实体.在EF 5.0/6.0中,存在POCO 实体和动态代理实体两种. ...
- Autofac - 方法注入
方法注入, 其实就是在注册类的时候, 把这个方法也注册进去. 那么在生成实例的时候, 会自动调用这个方法. 其实现的方法, 有两种. 准备工作: public interface IAnimal { ...
- BPM配置故事之案例3-参与者与数据自动加载
这才过了两天,阿海又来了. 阿海:公司决定改进管理方式,以后物资申请的申请人和申请部门要写具体使用人的名字和部门了. 小明:不是要让我改回去吧? 阿海:那太麻烦了,你能不能把申请人改成选择,选好人自动 ...