首先,不推荐在ASP.NET后台中,启动Long-Running的任务。因为无论是用的Task还是ThreadPool.QueueUserWorkItem,ASP.NET不会知道它们在后台运行,这会产生一些问题,比如:

当修改web.config的时候,会触发Appdomain被回收(尽管此时IIS web服务器进程w3wp.exe仍然活着),IIS本身也会每29小时回收应用程序池,这都会导致后台线程被终止,从而引发异常。

当ASP.NET要回收AppDomain,它会让已经存在的请求处理完再回收,ASP.NET和IIS服务器也知道这些请求正在运行,所以等它们完成。问题是ASP.NET不知道任何后台线程比如一个计时器或者其他,它只知道和request相关的操作。

事实上,在后台长时间的运行某些任务实在不是web server该做的事情,通常都可以用其他的方式来避免这样做,比如:

用console application和windows任务管理器,或者使用Windows服务等。

但是,如果确定要这样做,那么在ASP.NET中也有些办法保证后台任务能够安全的退出。

方法一,使用IRegisteredObject接口。

通过IRegisteredObject接口,并且调用HostingEnvironment.RegisterObject方法在ASP.NET中注册它。

当Appdomain要被回收的时候,会调用已注册对象中的IRegisteredObject中的Stop方法。

public interface IRegisteredObject { void Stop(bool immediate); }

已注册对象没有被取消注册(即没有调用HostingEnvironment.UnregisterObject方法来取消注册),那么Stop方法会被调用两次,第一次调用的时候,immediate参数为false,此时如果已注册对象已经停止了,那么应该调用 HostingEnvironment.UnregisterObject方法来取消注册。如果还不取消注册,那么30秒后,该方法会被再次执行(这是最后的机会),不同的是此时传入的immediate参数为true,此时注册对象必须先调用 UnregisterObject 方法然后返回;否则应用程序管理器将移除该对象的注册。

使用IRegisteredObject接口并不是用来处理后台Long-Running任务的,但是这个功能可以让你安全的退出后台任务。

举个例子,如下:

public class JobHost : IRegisteredObject
{
    private readonly object _lock = new object();
    private bool _shuttingDown;
    public JobHost()
    {
        HostingEnvironment.RegisterObject(this);
    }
    public void Stop(bool immediate)
    {
        lock (_lock)
        {
            _shuttingDown = true;
        }
        HostingEnvironment.UnregisterObject(this);
    }
    public void DoWork(Action work)
    {
        lock (_lock)
        {
            if (_shuttingDown)
            {
                return;
            }
            work();
        }
    }
}

如上面的代码,ASP.NET要回收Appdomain时,Stop 方法会被调用,这个方法会获得一个锁,在DoWork方法也获得同样的所,这样的话,DoWork在运行的时候,Stop方法不得不等待。

方法二,使用HostingEnvironment.QueueBackgroundWorkItem

HostingEnvironment.QueueBackgroundWorkItem 方法在.NET4.5.2中引入,QueueBackgroundWorkItem (QBWI) 通过ASP.NET运行时,注册后台任务。这样的话,由于ASP.NET知道有后台任务,他就不会立即回收Appdomain,当然,这不意味着后台任务可以做任何事情。当ASP.NET需要做回收的时候,它可以通过一个CancellationToken通知后台任务,并且等待30s让工作完成。如果30秒内没问出,那么这个工作也会消失了。关于QueueBackgroundWorkItem的更多细节,可以通过

http://blogs.msdn.com/b/webdev/archive/2014/06/04/queuebackgroundworkitem-to-reliably-schedule-and-run-long-background-process-in-asp-net.aspx学习。

方法二,使用HangFire

HangFire是一个开源的类库,提供简单的方法在ASP.NET中执行后台Long-Running任务。这个类库需要一些额外的存储上的支持,SQLServer,Redis或者MSMQ。HangFire的资料在http://hangfire.io/

 

参考资料

http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/

http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html

http://blogs.msdn.com/b/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx