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里。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM