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包,使基于应用 ...
随机推荐
- 重撸js_2_基础dom操作
1.node 方法 返回 含义 nodeName String 获取节点名称 nodeType Number 获取节点类型 nodeValue String 节点的值(注意:文本也是节点) 2.inn ...
- Matlab 高脚杯模型切片
前言:此文为去年我替人做的一个课题,觉得比较简洁,图形也比较美观,因此放在博文里 数据源我放到了百度云盘高脚杯数据源 有兴趣的可以下载数据,跑程序试一下.也可以单独看看代码,看下实现过程. 主函数 % ...
- Android Ormlite 学习笔记1 -- 基础
Ormlite 是一个开源Java数据实体映射框架.其中依赖2个核心类库: 1.ormlite-android-4.48.jar 2.ormlite-core-4.48.jar 新建项目,引用上面2个 ...
- CRL快速开发框架系列教程五(使用缓存)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
- 基于window7+caffe实现图像艺术风格转换style-transfer
这个是在去年微博里面非常流行的,在git_hub上的代码是https://github.com/fzliu/style-transfer 比如这是梵高的画 这是你自己的照片 然后你想生成这样 怎么实现 ...
- Java程序:从命令行接收多个数字,求和并输出结果
一.设计思想:由于命令行接收的是字符串类型,因此应先将字符串类型转化为整型或其他字符型,然后利用for循环求和并输出结果 二.程序流程图: 三.源程序代码: //王荣荣 2016/9/23 ...
- Eclipse使用Git教程
A:点击Window--->Show view--->other..--->Git Repositories--->[OK] B:克隆码云上的代码仓库 C:选择对应目录存储你的 ...
- Linux硬件IO的优化简介
Linux硬件IO的优化简介 首先简单介绍下有哪些硬件设备如下(由于硬件种类厂家等各种因素我就不在此多做介绍有兴趣的可以自行学习): 1.CPU:中央处理器,是计算机运算控制的核心部件之一,相当于人的 ...
- chattr用法
[root@localhost tmp]# umask 0022 一.chattr用法 1.创建空文件attrtest,然后删除,提示无法删除,因为有隐藏文件 [root@localhost tmp] ...
- DTO – 服务实现中的核心数据
在一个Web服务的实现中,我们常常需要访问数据库,并将从数据库中所取得的数据显示在用户页面中.这样做的一个问题是:用于在用户页面上展示的数据和从数据库中取得的数据常常具有较大区别.在这种情况下,我们常 ...