Hangfire初探

Hangfire 是什么?
Hangfire 是一个定时任务的管理后台,它拥有定时任务功能和及其相关的管理后台界面。Hangfire 原生使用 .NET 开发的,同时支持 .NET.NET Core 框架,所以可同时运行在 Windows 和 非 windows 平台上。
Hangfire 可以做什么?
其实上面已经提到了,可以像 Quartz.NET 一样自定义定时任务,但 Hangfire 提供了任务的 Web 管理界面,所以你可以很方便的查看任务状态管理任务,你也不需要远程登录 Windows Server 桌面重启或停止任务了,方便了很多,同时它提供持久化,即使程序挂掉或重启,上次未被执行或没执行完成的任务会继续执行。
是不是有点小期待,想知道 Hangfire 怎么用,别急下面就介绍如何使用,如果你一点都不期待,你也可以稍微花些许时间看一眼,没准也许你会不经意喜欢上它。好了,废话不说了,重点马上来!
Hangfire 如何使用?
这次我打算依旧使用 ASP .NET Core 作为本次的例子开发框架,例子代码地址会放在文章最后。在终端输入以下代码创建项目:

mkdir ~/Development/HangfireJobs && cd ~/Development/HangfireJobs
dotnet new sln
dotnet new mvc -n HangfireJobsApp
dotnet sln add ./HangfireJobsApp/HangfireJobsApp.csproj
cd ./HangfireJobsApp
dotnet restore
dotnet build
dotnet run
...

现在一个基本的 Asp .Net Core MVC 项目在你本地创建并运行起来了,如下图所示:
HangfireJobsApp
接下来在终端输入以下代码,来添加Hangfire相关的包

dotnet add package HangFire --version 1.6.19
dotnet add package HangFire.Redis.StackExchange --version 1.7.2
dotnet build

这里任务持久化使用 Redis,所以添加了 HangFire.Redis.StackExchange nuget包,这是个三方的不是原作者开发的,原生的 Redis 包需要商业收费,但我觉得这个包暂时够用了。接下来,使用 VSCode 打开 Startup.cs 文件,敲入写入以下代码:

public void ConfigureServices(IServiceCollection services)
{
    // 自动生成代码忽略

    // 注入Hangfire
    services.AddHangfire(cfg=>{
        cfg.UseRedisStorage(Configuration.GetConnectionString("Hangfire"));
    });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // 自动生成代码忽略

    // 启用Hangfire
    app.UseHangfireServer();
    app.UseHangfireDashboard();
}

最后在appsettings.json文件,加入以下配置:

{
  // ...

  "ConnectionStrings":{
    "Hangfire":"127.0.0.1:6379"
  }
}

因为持久化用 Redis,所以这里需要你配置 Redis 地址,那么运行你的项目,然后在浏览器上输入地址
HangfireJobApp02
到这里,你已经完成了大部分工作,剩下的就是实现具体任务啦。
不过别急,我们先看看 Hangfire 的任务类型。
Hangfire有三种类型的任务:FAF(fire adn forget)即一次性任务,计划任务(在某个时间点执行一次),还有就是最常用的周期性任务,可以设置某个时刻重复执行。还是用代码展示吧。

...
// 立即执行的单次任务
BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget"));
// 创建计划任务,5分钟后执行
BackgroundJob.Schedule(() => Console.WriteLine("Delayed"), TimeSpan.FromMinutes(5));
// 创建周期性任务,每隔2分钟执行一次
RecurringJob.AddOrUpdate("JobA", () => Console.WriteLine("Recurred"), Cron.MinuteInterval(2));
// 创建周期性任务,每天21:10执行,
RecurringJob.AddOrUpdate("JobB", () => Console.WriteLine(DateTimeOffset.Now), "10 21 * * *");
// 创建周期性任务,每天21:10执行
RecurringJob.AddOrUpdate("JobC", () => Test(), "10 21 * * *", TimeZoneInfo.Local);
...
public void Test()
{
    Console.WriteLine(DateTimeOffset.Now);
}

在代码里,你发现周期任务我多列了2个,而且貌似执行时间都相同,然而还是不同的。你需要注意时区的问题,Recurring 添加任务时默认是UTC-0时区,所以当你写代码创建任务时,要注意是否需要指定时区,否则任务可能不会按照你预期的时间执行。
其实这段代码,还有些东西需要注意,也许你注意到了,就是在控制台输出的时间,在最后两个任务是不一致的,你或许会说是因为时区问题,即使改成同一时区输出结果也还是不同,你可以想想为什么。

JobActivator

上面的创建任务的例子确实有些简单了,你想要更优雅的创建任务,比如通过依赖注入的方式。别急 Hangfire 已经提供了解决方法,没错就是 JobActivator,它允许你通过依赖注入这样更方便的方式调用你自定义的任务。
在这个例子里,我分别创建三个类:HFSimpleJob,HFJobActivator,HFJobActivatorScope,代码如下:

// HFSimpleJob.cs
...
public class HFSimpleJob
{
    int counter = 0;
    public void Run()
    {
        counter++;
        Console.WriteLine($"当前时间是:{DateTimeOffset.Now},当前计数:{counter}");
    }
}
// HFJobActivator.cs
...
public class HFJobActivator : JobActivator
{
    readonly IServiceScopeFactory _serviceScopeFactory;
    public HFJobActivator(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }
    public override JobActivatorScope BeginScope(JobActivatorContext context)
    {
        return new HFJobActivatorScope(_serviceScopeFactory.CreateScope());
    }
}
// HFJobActivatorScope.cs
public class HFJobActivatorScope : JobActivatorScope
{
    IServiceScope _serviceScope;
    public HFJobActivatorScope(IServiceScope serviceScope)
    {
        if (serviceScope == null) throw new ArgumentNullException(nameof(serviceScope));
        _serviceScope = serviceScope;
    }
    public override object Resolve(Type type)
    {
        return _serviceScope.ServiceProvider.GetService(type);
    }
    public override void DisposeScope()
    {
        _serviceScope.Dispose();
    }
}

HFSimpleJob就是一个简单的模拟业务的任务类,就不细说了,重点说说后面两个。HFJobActivator这个类继承自 JobActivator ,它用来在任务执行前实例化你注入的任务类的 Activator,而 HFJobActivatorScope 继承自 JobActivatorScope,这个类的作用就是调用 IOC 容器实例化之前注入的类。
OK,现在再加上如下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<HFSimpleJob>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
    ....

   // 添加自定义依赖注入相关类
   GlobalConfiguration.Configuration.UseActivator(new HFJobActivato(serviceProvider.GetService<IServiceScopeFactory>()));
   // 启用Hangfire
   app.UseHangfireServer();
   app.UseHangfireDashboard();

   // 依赖注入
   RecurringJob.AddOrUpdate<HFSimpleJob>("JobIOCA", p => p.Run(), "*/2 * * **");
}

编译运行代码,就可以看到类似下面截图的内容了,最后一个就是使用依赖注入的方式创建的任务:

HangfireApp03
是不是觉得这样写,没感觉简单多少,这里只是简单举例讲述怎么用,需要自己具体问题具体分析。我这里写一个我的用例,我先定义任务接口,然后所有的业务任务类都实现这个接口,然后使用反射将这些类通过 IOC 的方式批量注入,这样后面新增其它业务的任务,就无须关注业务以外的任务注册调用的代码了,只要实现那个接口测试编译通过即可,这样就简单了很多。这里就不写怎么实现了,你可以自己想想如何,我会把这部分功能放在Demo里。

猜你喜欢

转载自www.cnblogs.com/ShawnHao/p/9368736.html
今日推荐