FluentScheduler is a better solution for cronjob in asp.net . https://www.nuget.org/packages/FluentScheduler

在一个网站中,经常需要设定一些任务能够定时执行。

比如一些统计数据,例如网站会员的积分,积分的计算可能涉及很多因素和变量,而且需要累计很多条记录才能得到结果,如果每次显示会员信息的时候,都是实时计算,那么可想而知计算量非常大。如果还需要对所有会员的积分排序后在显示列表,那就更是不可能完成的任务了。因此,显然需要把积分的计算和显示分开,计算不是实时的,当然这样就会导致一定的不一致情况发生。这就要看对一致性的要求了。对于不赢房子不赢地的会员积分,稍微更新晚一点倒是无所谓的。

再比如,现在很多网站会根据会员的行为,颁发一些荣誉勋章,颁发的条件都是根据会员在网站行为来判断的。显然不能当一名会员在网站做了任何一个行为,都立即计算一次是否符合颁发各种勋章的条件。因此,也只能定期地计算一次。

因此关键就在于,非实时的计算会员积分,必定要求定时地执行,比如每半小时或一分钟计算一次,具体时间间隔就根据业务的实际需要了。

那么对于 ASP.NET 开发的网站,应该如何实现在后台定时执行某个任务呢? 如果你在Google上用 cron job ASP.NET 搜索,会搜到很多帖子,很显眼的是 Stack Overflow 上的问题,被大多数人顶的答案是这样的:

ASP.NET 不是完成这个工作的工具,你应该单独做一个独立的程序,然后通过设定为任务计划,使该程序定期执行,要么干脆做成Service。而 ASP.NET 应该只被用来完成 请求/响应 模型的工作。

这个答案很有道理,确实应该如此。但是对于一个规模并不大的网站,如果能够在 ASP.NET 内部解决这个问题,还是很有吸引力的。

实际上解决方案还是有的,并且是经过实际应用的,StackOverflow 的 Jeff Atwood 发过一个帖子,讲解了一个方案,并且提到,StackOverflow 早期就是用这个方案每60秒钟计算会员的徽章等数据的。当然,他也提到后随着规模增大,他们已经不使用这个方案了。有兴趣的可以参见:http://blog.stackoverflow.com/2008/07/easy-background-tasks-in-aspnet/

我这里简单描述一下这个方案,我用lambda表达式的方式改写了一下,代码看起来简单一些。

public static void AddTask(int seconds, Action todo)
{
    HttpRuntime.Cache.Insert(
        Guid.NewGuid().ToString(),
        0,
        null,
        DateTime.Now.AddSeconds(seconds),
        Cache.NoSlidingExpiration,
        CacheItemPriority.NotRemovable,
        (key, value, reason) => 
        {
            todo.Invoke();
            AddTask(seconds, todo);
        });
}

就这么一个非常简单的函数,然后在 global.asax 里面的调用这个函数即可。

void x()
{
    Debug.WriteLine(DateTime.Now);
}

void x2()
{
    Debug.WriteLine("---" + DateTime.Now);
}

protected void Application_Start()
{
    AddTask(40, x);
    AddTask(60, x2);
    //.......
}

上面的代码设定了两个void类型的方法,然后在 Application_Start() 中设定二者的执行时间间隔,一个40秒执行一次,一个60秒执行一次。在IDE的output窗口中,可以看到运行的结果如下:

2013/5/12 18:09:00
---2013/5/12 18:09:20
2013/5/12 18:09:40
---2013/5/12 18:10:20
2013/5/12 18:10:20
2013/5/12 18:11:00
---2013/5/12 18:11:20
2013/5/12 18:11:40
2013/5/12 18:12:20
---2013/5/12 18:12:20

可以看到,一个40秒打印一行,另一个60秒打印一行。具体要做什么,就看你的了!

需要说明的是,这个方案的原理是利用了ASP.NET的缓存机制,向ASP.NET的缓存集合中添加一个缓存项,设定过期的时间,当到达这个时间的时候,会执行一个回调函数,就把你要做的事情放到这个回调函数里,并且在执行要做的事儿之后,再次加入一个缓存项,如此不断地往复进行。

这里存在一个问题就是,在默认情况下,ASP.NET 每20秒回收一次缓存项,因此,这个方法不能把时间间隔设置的太小,我觉得至少分钟级别比较合适。

最后需要再次提醒的是,这个方法可以算是一个 hack 方法,并不是一个常规的优雅的做法,在 Jeff Atwood 的帖子后面,很多人提出了反对意见,大家有兴趣可以看一看。而且在这个帖子的半年多已后的2009年初,Stack Overflow 就不再使用这个方法,而改用更正常的方式来实现了。但是你要知道,Stack Overflow 的访问量在初期发展异常迅猛,在2009年初的时候,Alexa 排名已经升到全世界的1000多名了。Stack Overflow 半年的路,很多网站可能要走很多年,甚至很多年也到不了,因为全世界也就1000个网站可以排到前1000名。

enter image description here

好了,希望这个小技巧对你有所帮助。说实话,这个技巧可以解决大问题滴。