FluentScheduler介紹
github地址:https://github.com/fluentscheduler/FluentScheduler
FluentScheduler是一個簡單的任務調度框架,使用起來非常方便,這個框架也是我在搜索iis預加載的時候偶然間發現的,立馬拿來試用一下,感覺爽呆了,當然還有Quarz.Net之類的其他任務管理框架,不過看配置似乎有點麻煩,反正除了timer我啥也沒用過...
之前還花費了很長一段時間自己寫了一套定時任務的框架,如今看到FluentScheduler我已經決定將之前的廢棄了...
好吧,廢話不多說,框架調用非常簡單,所以直接上代碼了,其實我做的只不過是把英文翻譯一下
FluentScheduler使用
.net 框架:.net framework 4.5
項目:.net mvc5
如果要在winform,wpf之類的項目中使用是完全沒有問題的,因為本文最終的目標是實現將該web項目作為一個定時任務的服務,所以選擇了以上的架構
1.引用nuget包:FluentScheduler
2.Application_Start函數加上:
//初始化任務管理器
JobManager.Initialize(new MyRegistry());
3.MyRegistry.cs
public class MyRegistry : Registry
{
public MyRegistry()
{
// Schedule an IJob to run at an interval
// 立即執行每10秒一次的計划任務。(指定一個時間間隔運行,根據自己需求,可以是秒、分、時、天、月、年等。)
Schedule<MyJob>().ToRunNow().AndEvery(10).Seconds();
// 立即執行每10秒一次的計划任務。如果本次任務沒有結束,下一次的任務則不會開始,禁止並行運行
Schedule<MyJob>().NonReentrant().ToRunNow().AndEvery(10).Seconds();
//在每天的21:15執行計划任務
Schedule(() => Console.WriteLine("It's 9:15 PM now.")).ToRunEvery(1).Days().At(21, 15);
// 立即執行一個在每月的第一個星期一 3:00 的計划任務
Schedule(() => Console.WriteLine("It's 3:00 AM now.")).ToRunNow().AndEvery(1).Months().OnTheFirst(DayOfWeek.Monday).At(3, 0);
//在每周一的21:15執行計划任務
Schedule(() => Console.WriteLine("It's 9:15 PM now.")).ToRunEvery(1).Weeks().On(DayOfWeek.Monday).At(21, 15);
}
}
上面需要注意的是NonReentrant
函數的使用,在某些特殊的業務里可能任務執行的時間比定時循環的間隔時間要長,這時候你就要考慮是否允許並行運行兩個同樣的任務,NonReentrant就是用來解決這個問題的
4.MyJob.cs
public class MyJob : IJob, IRegisteredObject
{
private readonly object _lock = new object();
private bool _shuttingDown;
private static Logger logger = LogManager.GetCurrentClassLogger(); //初始化日志類
public MyJob()
{
HostingEnvironment.RegisterObject(this);
}
public void Execute()
{
try
{
lock (_lock)
{
if (_shuttingDown)
return;
logger.Info("開始工作:" + DateTime.Now);
Thread.Sleep(60*1000);
logger.Info("工作結束:" + DateTime.Now);
}
}
finally
{
HostingEnvironment.UnregisterObject(this);
}
}
public void Stop(bool immediate)
{
logger.Info("調用stop:" + DateTime.Now);
lock (_lock)
{
logger.Info("lock結束:" + DateTime.Now);
_shuttingDown = true;
}
HostingEnvironment.UnregisterObject(this);
}
}
上面是一個簡單的示例,所有的業務邏輯都在Execute函數中執行,如果不在web項目中運行,則不需要實現IRegisteredObject接口以及stop函數,所有的業務代碼均在Execute
函數中執行
在ASP.NET中作定時任務
在之前我們也有部分項目用widowsservice來做定時任務,但是弊端很明顯,調試太麻煩,發布也麻煩,自動發布更難實現
相比之下web服務器就容易管理的多了
實際上在asp.net 中的定時任務和FluentScheduler框架並沒有什么必然的聯系,你也可以用timer或其他的任何方式來實現,但是所有的這些實現方式都避免不了面對一個問題:IIS的回收機制
因為有了回收機制的存在,所以在asp.net中做定時任務就會面臨兩個問題:
1.任務沒有執行完成線程就被回收了
2.線程回收之后,只有在下一次訪問網站的時候任務才會再次啟動
首先我們來解決第一個問題:
對於iis的回收,我們需要做的其實並不是阻止它的回收,實際上我試過各種方式都無法完全阻擋iis的回收,不知道是否是方法沒有用對。
但是我們可以保證當前的任務執行完畢再進行回收
方式就是實現IRegisteredObject
接口,以上面的MyJob類為例,我們通過調用HostingEnvironment.RegisterObject方法在ASP.NET中注冊它
通過調用HostingEnvironment.UnregisterObject方法釋放服務
當Appdomain要被回收的時候,會調用已注冊對象IRegisteredObject中的Stop方法。
//
// 摘要:
// Requests a registered object to unregister.
//
// 參數:
// immediate:
// true to indicate the registered object should unregister from the hosting environment
// before returning; otherwise, false.
void Stop(bool immediate);
在第一次調用stop方法時,參數為false,執行完畢后,如果沒有調用HostingEnvironment.UnregisterObject
函數,隔30秒stop方法會再次被調用,參數為true,如果仍然沒有調用HostingEnvironment.UnregisterObject
函數,該服務就會被移除
不過我們使用的過程中並不會考慮第二次的調用,因為在第一次stop函數被調用的時候我們就會lock住正在執行的任務,並且一直到任務執行完成再釋放lock,最后調用HostingEnvironment.UnregisterObject保證任務正常退出
對於這個流程上面的Myjob就是FluentScheduler提供的一個示例
IIS預加載
應用程序池回收之后,如果沒有人訪問網站,w3wp是不會啟動的,那也就代表着我們的定時任務就不會啟動了,所以我們需要在程序池被回收之后模擬訪問一下該網站,我們可以通過寫一個定時的程序每隔一秒鍾訪問一遍該網站來解決這個問題,但是為了解決這個問題多寫一個程序並沒有必要,因為微軟已經提供了一個網站預加載的功能,每當應用程序池被回收,系統就會啟動一個進程模擬訪問一遍網站。這個功能似乎是iis7之后就有了,我下面演示的iis10的界面,其他版本的界面可能會稍微有所不同
1.修改應用程序池啟動模式
2.開啟對應網站預加載
3.增加配置編輯器,編寫默認預加載的請求頁面
至此,我們的服務就可以正常的運行啦