1. 程式人生 > >.net core下定時任務的實現

.net core下定時任務的實現

在上一篇[.net core下驗證碼及二維碼登入的實現]主要介紹了驗證碼及二維碼的實現,本篇主要介紹下在 .net core下如何實現定時任務

Hangfire介紹

Hangfire作為一款高人氣且容易上手的分散式後臺執行服務,支援多種資料庫。在 .net core的環境中,由Core自帶的DI管理著生命週期,免去了在NF4.X環境中配置always running的麻煩,真正做到開箱即用。

官方文件點這裡

相較於quartz.net相比,最大的優點是有個自帶的監控介面,比較方便。但有一點,Hangfire只支援分鐘級別的定時任務,如果想用秒級別的定時任務,那可能Hangfire就不滿足你的需求了。

Hangfire基礎

基於佇列的任務處理(Fire-and-forget jobs)

基於佇列的任務處理是Hangfire中最常用的,客戶端使用BackgroundJob類的靜態方法Enqueue來呼叫,傳入指定的方法(或是匿名函式),Job Queue等引數.(類似MQ)

var jobId = BackgroundJob.Enqueue(
    () => Console.WriteLine("Fire-and-forget!"));

在任務被持久化到資料庫之後,Hangfire服務端立即從資料庫獲取相關任務並裝載到相應的Job Queue下,在沒有異常的情況下僅處理一次,若發生異常,提供重試機制,異常及重試資訊都會被記錄到資料庫中,通過Hangfire控制面板可以檢視到這些資訊。

延遲任務執行(Delayed jobs)

延遲(計劃)任務跟佇列任務相似,客戶端呼叫時需要指定在一定時間間隔後呼叫:

var jobId = BackgroundJob.Schedule(
    () => Console.WriteLine("Delayed!"),
    TimeSpan.FromDays(7));
定時任務執行(Recurring jobs)

定時(迴圈)任務代表可以重複性執行多次,支援CRON表示式:

RecurringJob.AddOrUpdate(
    () => Console.WriteLine("Recurring!"
), Cron.Daily);
延續性任務執行(Continuations)

延續性任務類似於.NET中的Task,可以在第一個任務執行完之後緊接著再次執行另外的任務:

BackgroundJob.ContinueWith(
    jobId,
    () => Console.WriteLine("Continuation!"));

程式碼實現

在我的專案中,實際只用到了定時任務(用於跑一些報表,郵件預警之類的需求),其他的業務場景感覺還是MQ更加適合。

下面來看看 .net core下具體的程式碼實現:

首先需要引用元件Hangfire.dllHangfire.MySqlStorage.dll,我使用的是Mysql。

Startup.cs中的ConfigureServices中初始化資料庫:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddHangfire(x => x.UseStorage(new MySqlStorage(CONNECTION_STRING)));
}

Configure啟動你的Hangfire服務:

var jobOptions = new BackgroundJobServerOptions
{
    //Queues = new[] { "test", "default" },//佇列名稱,只能為小寫
    WorkerCount = Environment.ProcessorCount * 5, //併發任務數
    ServerName = "hangfire1",//伺服器名稱
};

app.UseHangfireServer(jobOptions);//啟動Hangfire服務

同時你可以在Configure下啟動你的監控應用:

var options = new DashboardOptions
{
    Authorization = new[] { new HangfireAuthorizationFilter() }
};
app.UseHangfireDashboard("/job_dashboard", options);

這樣啟動後就可以看到你的監控後臺了,輸入地址/job_dashboard

1

一些小改動

由於專案可能經常會重新部署,所以在專案啟動時我會預設重新啟動定時任務:

Startup.cs直接啟動JobService.Register():

public static async void Register()
{
    var jobKeys =await JobMonitorServices.GetAllJobKey();

    if (!jobKeys.Any())
        return;

    //暫時只開一個queue,後期可擴充套件
    foreach(var keyModel in jobKeys)
    {
        var key = keyModel.Key.Split(':')[1];
        RecurringJob.AddOrUpdate(key, () => JobMonitorServices.Execute(key), keyModel.Value);
    }        
}

至於GetAllJobKey方法,是我直接從資料庫裡取的:

public static async Task<List<HashModel>> GetAllJobKey()
{
    string sql = $@"SELECT distinct `Key`,`Field`,`Value` FROM Hash WHERE Field='Cron';";
    using (var conn = DatabaseManager.GetConnection(DatabaseManager.JOB_DBName))
    {
        await conn.OpenAsync();
        return (await conn.QueryAsync<HashModel>(sql)).ToList() ;
    }
}

這樣的話我可以同時暴露出對應的新增修改job介面了,這樣方便我們直接通過服務去新增job,或者修改job的觸發時間:

/// <summary>
/// 新增or更新Job
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Post([FromBody]JobKeyRequestModel entity)
{
    var result = JobMonitorServices.CheckEnableJobKey(entity);
    if (result.Result)
        RecurringJob.AddOrUpdate(entity.Key, () => JobMonitorServices.Execute(entity.Key), entity.Cron,TimeZoneInfo.Local);
    return AssertNotFound(result);
}

總結

Hangfire對於小專案來說用起來還是比較方便的,但對於精度要求和效能要求比較高的專案來說,還需要考量下。畢竟沒有壓測過,不知道效能怎麼樣。