在ASP.NET里运行定时任务,这是个老生常谈的话题了,撇开那些用per request搞定屌丝办法,目前最好的解决办法只有2种:
- 如果你有大微软的Azure,可以直接在网站服务中找到Jobs,自己看一下就会了
- 如果你是屌丝,买不起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
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. 有图有真相: