在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

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. 有图有真相: