如何處理定期的需求
相信很多人會遇到需要定期做某些事情的狀況,例如每分鐘去計算一次資料,或者一分鐘跟資料庫撈取資料的需求,這時如果寫Windows Service或是Console程式再搭配Windows排程來設定是一個解決方法,但是沒辦法看到排程的相關資訊,而且還要記得去作業系統設定服務或是排程之類的,不過今天可以有另外一個選擇,請大家繼續看下去。
hangfire
「前輩,今天需要做一個每分鐘爬取匯率的功能耶,我沒有做過這類的」
今天大頭要來做一個一分鐘爬取匯率的功能,但是因為沒有做過所以很苦惱,這時看到老K走進來所以急急忙忙地詢問一下。
「恩~~~你就做一個while然後Task.Delay(60000)不就好了。」
老K聽到大頭的問題後就這樣回答他,不過大頭聽了之後的表情不是很滿意,這時候老K笑笑地對他說。
「哈哈哈,開玩笑的啦你可以寫一個寫一個Console程式,然後看是掛CronJob還是Windows排程就好了。」
大頭聽了這個回答之後,雖然不是很滿意但是看起來準備要去開始動工了,這時老K叫住了他。
「好了好了不鬧你了,剛剛都是開玩笑的,你可以去研究一下hangfire。」
這時候小光剛好走了過來,老K看到他後跟大頭這麼說。
「你就跟他一起去研究一下吧。」
環境設定
在開始開發排程程式之前先設定一下環境。所以先輸入以下指令來加載套件。
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.Autofac
dotnet add package Hangfire.MemoryStorage
這裡說明一下,因為hangfire會把排程以及要輸入的參數存放起來,不過這邊我們可以選擇把資料放在記憶體中,如此可以避免大量存取資料庫的問題,不過要小心記憶體增長的問題,所以要去清理已經完成的資料。在加載套件完成之後我們來註冊排程到我們的程式裡面,首先先在Statrup.Configure
加入以下內容。
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(provider));
app.UseHangfireServer(new BackgroundJobServerOptions
{
SchedulePollingInterval = TimeSpan.FromMilliseconds(1000)
});
app.UseHangfireDashboard("/hangfire",
new DashboardOptions()
{
Authorization = new[] { new AllowAllConnectionsFilter() },
IgnoreAntiforgeryToken = true
});
其中SchedulePollingInterval
是設定多久檢查排程一次,預設是15秒,這部分相關內容可以參考使用 Hangfire OWIN 建立非同步任務,再來UseHangfireDashboard
是建立內建的hangfire的操作畫面,GlobalConfiguration.Configuration.UseActivator
是使用autofac
的注入然後相關的類別HangfireActivator
及AllowAllConnectionsFilter
如下。
/// <summary>
/// hangfire activator
/// </summary>
public class HangfireActivator : JobActivator
{
/// <summary>
/// service provider
/// </summary>
private readonly IServiceProvider _provider;
/// <summary>
/// Initializes a new instance of the <see cref="HangfireActivator" /> class.
/// </summary>
/// <param name="provider">service provider</param>
public HangfireActivator(IServiceProvider provider)
{
_provider = provider;
}
/// <summary>
/// activate job
/// </summary>
/// <param name="type">active type</param>
/// <returns>job instance</returns>
public override object ActivateJob(Type type)
{
return _provider.GetService(type);
}
}
/// <summary>
/// for hangfire needless authorization
/// </summary>
public class AllowAllConnectionsFilter : IDashboardAuthorizationFilter
{
/// <summary>
/// Authorize function
/// </summary>
/// <param name="context">dashboard context</param>
/// <returns>is authorize</returns>
public bool Authorize(DashboardContext context)
{
// Allow outside. You need an authentication scenario for this part.
// DON'T GO PRODUCTION WITH THIS LINES.
return true;
}
}
在註冊玩排程之後,接下來要Startup.ConfigureServices
內寫說要使用排程,這時我們加入以下內容。
services.AddHangfire(x => x.UseMemoryStorage());
services.AddHangfireServer();
這裡使用記憶體來放置排程相關資料以及輸入參數。到這邊為止關於環境的設定就告一段落。接下來要再說明如何建立排程。
建立排程
這邊開始說明如何建立一個排程,這部分透過autofac
注入後使用上其實相當簡單,其實只要建立一個類別並公開一個方法即可,這邊要注意的是如果當異常發生時hangfire會有重試的機制,如果不要重試的話要在該方法上加上Attribute即可,如下所示。
[AutomaticRetry(Attempts = 0)]
public void DoJob(DateTime datetime)
還有要注意這邊輸入的參數要簡單,若輸入的參數是參考型別然後排程時間又九的狀況下有可能該參數被gc了,這樣可就gg了。最後來看看如何使用排程。
使用排程
這邊常用的設定排程語法有下列兩種方式,請看以下範例。
RecurringJob.AddOrUpdate<Schedule>("DoJob",
x => x.DoJob(),
Cron.Daily(18));
RecurringJob.AddOrUpdate<Schedule>("DoJob3",
x => x.DoJob(),
"0/30 * * * *");
BackgroundJob.Schedule<Schedule>("DoJob2",
x => x.DoJob(),
TimeSpan.FromSeconds(3));
首先RecurringJob.AddOrUpdate
是定期性的排程,所以這邊可以用Cron表達示或是使用Cron來產生Cron表達示,BackgroundJob.Schedule
示延遲多久後觸一一次性的排程,然後還有一種BackgroundJob.Enqueue
是立即觸發一次性的排程。這裡要注意當排程的參數需要輸入時不要以下列方式寫。
BackgroundJob.Schedule<Schedule>("DoJob2",
x => x.DoJob(DateTime.Now),
TimeSpan.FromSeconds(3));
因為他會把當下時間紀錄程每次排程的參數之一,所以如果需要每次排程都需要輸入當下時間的話就要建立一個方法是不需要參數輸入的,並在該方法內使用DateTime.Now
才對。
後記
今天跟著小光還有大頭學習如何透過hangfire來設定排程,並且學習到如何建立一個排程以及透過di的方式把需要的元件給注入,希望對大家的dotnetcore排程設計有所幫助,這邊還有另外一個Quartz.NET另外一個排程套件,給有興趣的人看一下。