在ASP.NET里运行定时任务,这是个老生常谈的话题了,撇开那些用per request搞定屌丝办法,目前最好的解决办法只有2种:
1. 如果你有大微软的Azure,可以直接在网站服务中找到Jobs,自己看一下就会了
2. 如果你是屌丝,买不起Azure,就用本文介绍的WebBackgrounder搞
由于ASP.NET是服务器端Web框架,所以一般而言,一个操作的往往是只有收到客户端Request之后才能执行的,如果网站一直没人访问,没有Request进来,如何执行代码呢?定时任务就是这种坑爹场景。
还好,大微软的MVC帝、ASP.NET小王子haacked蜀黍给我们写了个 http://www.nuget.org/packages/WebBackgrounder/ 专门捣鼓这种场景。作为一个屌丝程序猿,和大牛的区别就在于“好编程,不求甚解”。所以我们不必在意它是怎么实现的,只要会用就行了。我在公司的项目中就用了一下,感觉很爽。
1. 首先,你要用高大上的ASP.NET 4.5(用低版本的都去吃翔吧
为了装逼,我们要通过命令行安装WebBackgrounder的NuGet包:
PM> Install-Package WebBackgrounder
2. 新建一个静态类,名字任意,我取名为“WebBackgrounderSetup.cs”(名字也是可以反映逼格的,最好以Setup结尾
3. 在namespace外面,注意,一定一定要在namespace的外面!!!!!!加上这两行代码,用来注册在ASP.NET应用启动和结束时要触发的方法。
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(你的命名空间.WebBackgrounderSetup), "Start")] [assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(你的命名空间.WebBackgrounderSetup), "Shutdown")]
4. 根据你的需求,创建一个类,继承Job,比如我这个:
/// <summary> /// Job To Refresh Project Tree into Memory /// </summary> public class GetCucumberProjectTreeJob : Job { public GetCucumberProjectTreeJob(TimeSpan interval, TimeSpan timeout) : base("GetCucumberProjectTree Job", interval, timeout) { } public override Task Execute() { return new Task(() => { Logger.Debug("Refreshing Project Tree..."); Utils.Projects = Utils.GetTreeFromDisk(); }); } }
注意构造函数,是继承Job父类的,"GetCucumberProjectTree Job"是你的Job的名字,interval是每次执行间隔时间,timeout是单次操作的超时时间,如果超时,这次Job就会被撸掉。Execute方法里面的就是你要执行的代码,因为方法返回类型是个Task,所以要把你的代码包在Task里面。
5. 把你创建的这个Job塞到刚才的WebBackgrounderSetup里面。代码看起来就像是这样:
public static class WebBackgrounderSetup { static readonly JobManager _jobManager = CreateJobWorkersManager(); public static void Start() { _jobManager.Start(); } public static void Shutdown() { _jobManager.Dispose(); } private static JobManager CreateJobWorkersManager() { var jobs = new IJob[] { new GetCucumberProjectTreeJob(TimeSpan.FromSeconds(5 * 60), TimeSpan.FromSeconds(20)), }; var coordinator = new SingleServerJobCoordinator(); var manager = new JobManager(jobs, coordinator); manager.Fail(ex => Logger.Error("Web Job Blow Up.", ex)); return manager; } }
因为这里我只有一个Job要执行,所以IJobs[]数组里面只赛了一个对象。如果你有很多Job要调度,都可以往这个数字里塞。
我需要每5分钟执行一次Job,每次超时20秒,就这样写:
new GetCucumberProjectTreeJob(TimeSpan.FromSeconds(5 * 60), TimeSpan.FromSeconds(20))
其中写5 * 60是个很流行的代码装逼方法,可以增加可读性,并且编译器在编译时候其实是把计算后的结果编译进去的,所以不会每次执行都去计算,即保住了逼格,也不影响性能。
处理Job执行中的异常,用的是
manager.Fail
里面塞的是一个委托,take的是爆破的Exception对象,在这里我只是记录一下Log,你可以自由发挥。
6. 有图有真相:
阿迪王,俺Kasim来了~ 把妹太多,技能疏于磨练,来拜读一下好基友的文章。
闲话少说,直捣黄龙,请接题:这个作业调度的框架,你是如何host的?需要注册到window service上么?
WebBackgrounder 可以实现我每天在固定的一个时间时完成一个任务吗?请指教。。。谢谢!
楼主发表这篇文章的时候忘记吃药了吗?
错误 1 未能找到类型或命名空间名称“WebActivatorEx”(是否缺少 using 指令或程序集引用?) d:\HD\documents\visual studio 2012\Projects\WebScheduler\WebScheduler\WebBackgrounderSetup.cs
还要加入这个.... Install-Package WebActivatorEx -Version 2.0.0
如何去手动关闭呢?这个问题值得研究耶!
直接在 Global.asax.cs 里添加一个 Timer 不行吗?这么高大上的写法比 Timer 好在哪里呢
WebBackgrounder 这和qutarz的差别是啥?