文档目录

本节内容:

简介

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文档 - 后台作业和工作者的更多相关文章

  1. ABP文档笔记系列

    ABP文档笔记 - 模块系统 及 配置中心 ABP文档笔记 - 事件BUS ABP文档笔记 - 数据过滤 ABP文档笔记 - 规约 ABP文档笔记 - 配置.设置.版本.功能.权限 ABP文档笔记 - ...

  2. ABP文档 - 通知系统

    文档目录 本节内容: 简介 发送模式 通知类型 通知数据 通知重要性 关于通知持久化 订阅通知 发布通知 用户通知管理器 实时通知 客户端 通知存储 通知定义 简介 通知用来告知用户系统里特定的事件发 ...

  3. ABP文档 - Hangfire 集成

    文档目录 本节内容: 简介 集成 Hangfire 面板授权 简介 Hangfire是一个综合的后台作业管理器,可以在ABP里集成它替代默认的后台作业管理器,你可以为Hangfire使用相同的后台作业 ...

  4. ABP文档 - Quartz 集成

    文档目录 本节内容: 简介 安装 创建工作 调度工作 更多 简介 Quartz 是一个功能完整的开源工作调度系统,可用于最小的应用到一个大型的企业系统.Abp.Quartz 包简单地把Quartz集成 ...

  5. ABP文档笔记 - 通知

    基础概念 两种通知发送方式 直接发送给目标用户 用户订阅某类通知,发送这类通知时直接分发给它们. 两种通知类型 一般通知:任意的通知类型 "如果一个用户发送一个好友请求,那么通知我" ...

  6. ABP文档 - 目录

    ABP框架 概览 介绍 多层结构 模块系统 启动配置 多租户 集成OWIN 共同结构 依赖注入 会话 缓存 日志 设置管理 时间 领域层 实体 值对象(新) 仓储 领域服务 工作单元 领域事件(Eve ...

  7. ABP文档 - Javascript Api - AJAX

    本节内容: AJAX操作相关问题 ABP的方式 AJAX 返回信息 处理错误 HTTP 状态码 WrapResult和DontWrapResult特性 Asp.net Mvc 控制器 Asp.net ...

  8. ABP文档 - EntityFramework 集成

    文档目录 本节内容: Nuget 包 DbContext 仓储 默认仓储 自定义仓储 特定的仓储基类 自定义仓储示例 仓储最佳实践 ABP可使用任何ORM框架,它已经内置了EntityFrame(以下 ...

  9. ABP文档 - SignalR 集成

    文档目录 本节内容: 简介 安装 服务端 客户端 连接确立 内置功能 通知 在线客户端 帕斯卡 vs 骆峰式 你的SignalR代码 简介 使用Abp.Web.SignalR nuget包,使基于应用 ...

随机推荐

  1. Git 在团队中的最佳实践--如何正确使用Git Flow

    我们已经从SVN 切换到Git很多年了,现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用. Git的优点 Git的优点很多,但是这里只列出我认为 ...

  2. ASP.NET是如何在IIS下工作的

    ASP.NET与IIS是紧密联系的,由于IIS6.0与IIS7.0的工作方式的不同,导致ASP.NET的工作原理也发生了相应的变化. IIS6(IIS7的经典模式)与IIS7的集成模式的不同 IIS6 ...

  3. ExtJS 4.2 组件介绍

    目录 1. 介绍 1.1 说明 1.2 组件分类 1.3 组件名称 1.4 组件结构 2. 组件的创建方式 2.1 Ext.create()创建 2.2 xtype创建 1. 介绍 1.1 说明 Ex ...

  4. PowerShell过滤文件中的重复内容

    Get-Content -Path E:\test11\data.txt | Sort-Object | Get-Unique 源文件: AA0001 2014-06-30 15:27:13.073 ...

  5. CSS——关于z-index及层叠上下文(stacking context)

    以下内容根据CSS规范翻译. z-index 'z-index'Value: auto | <integer> | inheritInitial: autoApplies to: posi ...

  6. JDK动态代理

    一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...

  7. scanf类型不匹配造成死循环

        int i = 0; while (flag) { printf("please input a number >>> "); scanf("% ...

  8. spider RPC入门指南

    本部分将介绍使用spider RPC开发分布式应用的客户端和服务端. spider RPC中间件基于J2SE 8开发,因此需要确保服务器上安装了JDK 8及以上版本,不依赖于任何额外需要独立安装和配置 ...

  9. SpringMVC+Shiro权限管理【转】

    1.权限的简单描述 2.实例表结构及内容及POJO 3.Shiro-pom.xml 4.Shiro-web.xml 5.Shiro-MyShiro-权限认证,登录认证层 6.Shiro-applica ...

  10. 计算Div标签内Checkbox个数或已被disabled的个数

    先看下面的html: 计算div内的checkbox个数:$('#divmod input[type="checkbox"]').length 计算div内checkbox被dis ...