介绍
Hangfire是一个开源框架(.NET任务调度框架),可以帮助您创建,处理和管理您的后台作业,处理你不希望放入请求处理管道的操作。而我们使用的ABP框架中是有自己默认的后台管理作业者,你可以用Hangfire来替换ABP中默认实现的后台作业管理者。你可以对Hangfire使用相同的后台作业API。因此,你的代码将独立于Hangfire。但是,如果你喜欢,你也可以直接的使用 Hangfire 的API。
下面是一些Hangfire的基础用法的示例:
1、 基于队列的任务处理(Fire-and-forget)
延迟作业也只执行一次,但不会立即执行 - 只能在指定的时间间隔后执行。
var jobId = BackgroundJob.Schedule( () => Console.WriteLine("Delayed!"), TimeSpan.FromDays(7));
2、定时执行(Recurring)
按照指定的CRON计划, 重复执行的作业会被多次触发。
RecurringJob.AddOrUpdate( () => Console.WriteLine("Recurring!"), Cron.Daily);
3、延续性执行(Continuations)
延续性任务类似于.NET中的Task,可以在第一个任务执行完之后紧接着再次执行另外的任务:
BackgroundJob.ContinueWith( jobId, () => Console.WriteLine("Continuation!"));
4、延时执行任务(Delayed)
延迟作业也只执行一次,但不会立即执行 - 只能在指定的时间间隔后执行。
var jobId = BackgroundJob.Schedule( () => Console.WriteLine("Delayed!"), TimeSpan.FromDays(7));
5、批处理(Batches)
批处理是一组自动创建的后台作业。
var batchId = Batch.StartNew(x => { x.Enqueue(() => Console.WriteLine("Job 1")); x.Enqueue(() => Console.WriteLine("Job 2")); });
6、延时批处理(Batch Continuations)
批处理在父类完成后触发后台作业。
Batch.ContinueWith(batchId, x => { x.Enqueue(() => Console.WriteLine("Last Job")); });
7、后台进程(Background Process)
当你需要在应用程序的整个生命周期中连续运行后台进程时使用它们。
public class CleanTempDirectoryProcess : IBackgroundProcess { public void Execute(BackgroundProcessContext context) { Directory.CleanUp(Directory.GetTempDirectory()); context.Wait(TimeSpan.FromHours(1)); } }
创建
ABP框架应该是集成了 Hangfire的所以一般情况下应该是不需要再去安装NuGet程序集,但是如果你没有的话那么你就需要去NuGet引入这几个程序集:
Hangfire.Core
Hangfire.SqlServer
Hangfire.MemoryStorage
使用job或者worker必须添加 AbpHangfireModule 作为依赖 下面有示例。
执行
AbpZero框架已经集成了hangfire,但它默认是关闭的,我们可以在运行站点下的Startup.cs文件中把这行代码注释取消就行了(如果没有那就手动进行添加),代码如下:
//Hangfire (Enable to use Hangfire instead of default job manager) services.AddHangfire(config => { //使用Hangfire替代默认的任务调度 config.UseSqlServerStorage(_appConfiguration.GetConnectionString("Default")); });
一般在启用执行的前面加上你得执行判断条件,根据你的任务调度需求进行条件的限定:
//仅在后台服务启用
if (!_appConfiguration["Abp:Hangfire:IsEnabled"].IsNullOrEmpty() && Convert.ToBoolean(_appConfiguration["Abp:Hangfire:IsEnabled"]))
然后把判断参数我们写在框架的配置文件中 这样我们就能根据需求更轻松的更改我们的限制条件参数。
框架配置
我们在Startup.cs除了使用Hangfire替代默认的任务调度还需要进行如下配置:
启用HangfireServer这个中间件(它会自动释放)
app.UseHangfireServer();
然后启用Hangfire的仪表盘(可以看到任务的状态,进度等信息)
app.UseHangfireDashboard();
然后配置下前台路由
app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new AbpHangfireAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) } });
在Startup.cs你需要去注册job或者worker,具体根据你的业务需求去定义
//注册Worker var workManager = IocManager.Instance.Resolve<IBackgroundWorkerManager>(); workManager.Add(IocManager.Instance.Resolve<XxxxWorker>()); //注册job var jobManager = IocManager.Instance.Resolve<IBackgroundJobManager>(); jobManager.Enqueue<XxxxJob, int>(0);
后台Workers
可以根据官方的实现方式进行实现,那就是依赖具体的基类(PeriodicBackgroundWorkerBase)重写其DoWork()方法并设置其等待时间Timer,但是这样就会存在应用程序的耦合。为什么会耦合呢,假设以后想采用HangFire或Quartz.NET来调度工作者,我们就需要把所有工作类的基类进行修改,这不利于系统的维护和可扩展,而且采用官方实现无法监测和管控工作者。那么如何解决这一问题呢? 要消除工作者类对具体调度类的依赖,则只能让后台工作者类继承自不含调度实现的基类(BackgroundWorkerBase)或直接实现接口(IBackgroundWorker) 该基类以及接口含有 public override void Start(); public override void Stop(); public override string ToString(); public override void WaitToStop(); 。。。。。。 等方法的提供,可以灵活的进行调度类的设置。
然后进行worker的注入:
[DependsOn(typeof(AdminCoreModule))] [DependsOn(typeof(AbpHangfireAspNetCoreModule))] public class AdminWorkerModule : AbpModule { public override void PreInitialize() { Configuration.BackgroundJobs.UseHangfire(); } public override void Initialize() { IocManager.RegisterAssemblyByConvention(typeof(AdminWorkerModule).GetAssembly()); } }
框架接口文档配置
根据上面的Startup.cs的配置我们需要在框架的appsettings.json中:
"ConnectionStrings": { "Default": "Server=(localdb)\MSSQLLocalDB; Database=Magicodes.IChe; Trusted_Connection=True;" },
以及判断条件的参数配置: "Hangfire": { "IsEnabled": "true", "DashboardEnabled": "true" }
示例
省市区获取
首先根据其业务需求我们只需要在程序执行的时候运行一次所以我们使用job,首先在Startup.cs注册job:
var jobManager = IocManager.Instance.Resolve<IBackgroundJobManager>(); jobManager.Enqueue<DistrictDataInitJob, int>(0);
然后添加 AbpHangfireModule 作为依赖:
[DependsOn(typeof(AdminCoreModule))]
[DependsOn(typeof(AbpHangfireAspNetCoreModule))]
public class JobsModule: AbpModule { /// <summary> /// 行政区域提供程序 /// </summary> public static IDistrictsProvider DistrictsProvider { get; set; } public override void PreInitialize() { Configuration.BackgroundJobs.UseHangfire(); } public override void Initialize() { IocManager.RegisterAssemblyByConvention(typeof(JobsModule).GetAssembly()); } public override void PostInitialize() { } } 然后进行job的业务代码编写: private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IRepository<DistrictInfo, long> _districtInfoRepository; public DistrictDataInitJob(IRepository<DistrictInfo, long> districtInfoRepository, IUnitOfWorkManager unitOfWorkManager) { this._districtInfoRepository = districtInfoRepository; _unitOfWorkManager = unitOfWorkManager; } [UnitOfWork(IsDisabled = true)] public override void Execute(int args) { using (var unitOfWork = _unitOfWorkManager.Begin(new UnitOfWorkOptions() { Timeout = TimeSpan.FromMinutes(30) })) { if (_districtInfoRepository.GetAll().Any()) { return; } var result = JobsModule.DistrictsProvider.GetDistricts().Result; long id = 0; foreach (var item in result) { AddChildrenDistricts(ref id, item); } unitOfWork.Complete(); } var jobManager = IocManager.Instance.Resolve<IBackgroundJobManager>(); jobManager.Enqueue<DataInitJob, int>(0); } private void AddChildrenDistricts(ref long id, Magicdoes.Districts.Core.DistrictInfo district, long parentId=0) { id++; var districtInfo = new DistrictInfo() { Id = id, AreaCode = district.AreaCode, CityCode = district.CityCode, Name = district.Name, Level = (DistrictLevels)Enum.Parse(typeof(DistrictLevels), district.Level.ToString()), ParentId = parentId }; _districtInfoRepository.Insert(districtInfo); if (district.Children.Any()) { foreach (var item in district.Children) { AddChildrenDistricts(ref id, item, districtInfo.Id); } } } } 以上为个人对Hangfire的理解,有不对地方望各位耐心指教!