我可以使用线程在 IIS 上执行长时间运行的作业吗?

在 ASP.Net 应用程序中,用户单击网页上的一个按钮,然后通过事件处理程序在服务器上实例化一个对象,并调用该对象的一个方法。 这个方法需要一个外部系统来完成,这可能需要一段时间。因此,我想要做的是在另一个线程中运行这个方法调用,这样我就可以通过“ Your request has been ”将控制权返回给用户。 我相当乐意这样做,因为火和忘记,虽然它会更好,如果用户可以继续轮询对象的状态。

我不知道的是,即使用户会话过期,IIS 是否允许线程继续运行。 想象一下,用户触发事件,我们在服务器上实例化对象,并在新线程中触发方法。用户对“您的请求已提交”消息感到满意,并关闭了浏览器。最终,这个用户会话将在 IIS 上超时,但是线程可能仍在运行,正在工作。IIS 是允许线程继续运行,还是会在用户会话过期后终止线程并释放对象?

编辑: 从答案和评论中,我理解最好的方法是将长时间运行的处理移到 IIS 之外。除了其他所有问题,这个函数还处理应用程序域回收问题。在实践中,我需要在有限的时间内完成版本1,并且必须在现有的框架内工作,因此希望避免使用服务层,因此希望在 IIS 内部启动线程。实际上,这里的“长时间运行”只需要几分钟,而且网站上的并发性很低,所以应该没问题。但是,下一个版本肯定需要分割成一个单独的服务层。

53097 次浏览

You wouldn't want to use a thread from the IIS thread pool for this task because it would leave that thread unable to process future requests. You could look into Asynchronous Pages in ASP.NET 2.0, but that really wouldn't be the right answer, either. Instead, what it sounds like you would benefit from is looking into Microsoft Message Queuing. Essentially, you would add the task details to the queue and another background process (possibly a Windows Service) would be in charge of carrying out that task. But the bottom line is that the background process is completely isolated from IIS.

You can accomplish what you want, but it is typically a bad idea. Several ASP.NET blog and CMS engines take this approach, because they want to be installable on a shared hosting system and not take a dependency on a windows service that needs to be installed. Typically they kick off a long running thread in Global.asax when the app starts, and have that thread process queued up tasks.

In addition to reducing resources available to IIS/ASP.NET to process requests, you also have issues with the thread being killed when the AppDomain is recycled, and then you have to deal with persistence of the task while it is in-flight, as well as starting the work back up when the AppDomain comes back up.

Keep in mind that in many cases the AppDomain is recycled automatically at a default interval, as well as if you update the web.config, etc.

If you can handle the persistence and transactional aspects of your thread being killed at any time, then you can get around the AppDomain recycling by having some external process that makes a request on your site at some interval - so that if the site is recycled you are guaranteed to have it start back up again automatically within X minutes.

Again, this is typically a bad idea.

EDIT: Here are some examples of this technique in action:

Community Server: Using Windows Services vs. Background Thread to Run Code at Scheduled Intervals Creating a Background Thread When Website First Starts

EDIT (from the far distant future) - These days I would use Hangfire.

Can you create a windows service to do that task? Then use .NET remoting from the Web Server to call the Windows Service to do the action? If that is the case that is what I would do.

This would eliminate the need to relay on IIS, and limit some of its processing power.

If not then I would force the user to sit there while the process is done. That way you ensure it is completed and not killed by IIS.

Just create a surrogate process to run the async tasks; it doesn't have to be a windows service (although that is the more optimal approach in most cases. MSMQ is way over kill.

There is a good thread and sample code here: http://forums.asp.net/t/1534903.aspx?PageIndex=2

I've even toyed with the idea of calling a keep alive page on my website from the thread to help keep the app pool alive. Keep in mind if you are using this method that you need really good recovery handling, because the application could recycle at any time. As many have mentioned this is not the right approach if you have access to other service options, but for shared hosting this may be one of your only options.

To help keep the app pool alive, you could make a request to your own site while the thread is processing. This may help keep the app pool alive if your process runs a long time.

string tempStr = GetUrlPageSource("http://www.mysite.com/keepalive.aspx");




public static string GetUrlPageSource(string url)
{
string returnString = "";


try
{
Uri uri = new Uri(url);
if (uri.Scheme == Uri.UriSchemeHttp)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
CookieContainer cookieJar = new CookieContainer();


req.CookieContainer = cookieJar;


//set the request timeout to 60 seconds
req.Timeout = 60000;
req.UserAgent = "MyAgent";


//we do not want to request a persistent connection
req.KeepAlive = false;


HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream stream = resp.GetResponseStream();
StreamReader sr = new StreamReader(stream);


returnString = sr.ReadToEnd();


sr.Close();
stream.Close();
resp.Close();
}
}
catch
{
returnString = "";
}


return returnString;
}

There does seem to be one supported way of hosting long-running work in IIS. Workflow Services seem designed for this, especially in conjunction with Windows Server AppFabric. The design allows for application pool recycling by supporting automatic persistence and resumption of the long-running work.

I disagree with the accepted answer.

Using a background thread (or a task, started with Task.Factory.StartNew) is fine in ASP.NET. As with all hosting environments, you may want to understand and cooperate with the facilities governing shutdown.

In ASP.NET, you can register work needing to stop gracefully on shutdown using the HostingEnvironment.RegisterObject method. See this article and the comments for a discussion.

(As Gerard points out in his comment, there's now also HostingEnvironment.QueueBackgroundWorkItem that calls down to RegisterObject to register a scheduler for the background item to work on. Overall the new method is nicer since it's task-based.)

As for the general theme that you often hear of it being a bad idea, consider the alternative of deploying a windows service (or another kind of extra-process application):

  • No more trivial deployment with web deploy
  • Not deployable purely on Azure Websites
  • Depending on the nature of the background task, the processes will likely have to communicate. That means either some form of IPC or the service will have to access a common database.

Note also that some advanced scenarios might even need the background thread to be running in the same address space as the requests. I see the fact that ASP.NET can do this as a great advantage that has become possible through .NET.

We started down this path, and it actually worked ok when our app was on one server. When we wanted to scale out to multiple machines (or use multiple w3wp in a web garen) we had to re-evaluate and look at how to manage a work queue, error handling, retries and the tricky problem of correctly locking to ensure only one server picks up the next item.

... we realized we are not in the business of writing background processing engines so we looked for existing solutions and we landed up using the awesome OSS project hangfire

Sergey Odinokov has created a real gem which is really easy to get started with, and allows you to swap out the backend of how work is persisted and queued. Hangfire uses background threads, but persists the jobs, handles retries and gives you visibility into the work queue. So hangfire jobs are robust and survive all the vagaries of appdomains being recycled etc.

Its basic setup uses sql server as the storage but you can swap out for Redis or MSMQ when its time to scale up. It also has an excellent UI for visualizing all the jobs and their status plus allowing you to re-queue jobs.

My point is that while its entirely possible to do what you want in an background thread, there is a lot of work to make it scalable and robust. Its fine for simple workloads but when things get more complex I much prefer to use a purpose built library rather than go through this effort.

For some more perspective on options available check out Scott Hanselman's blog which covers off a few options for handling background jobs in asp.net. (He gave hangfire a glowing review)

Also as referenced by John its worth reading up Phil Haack's blog on why the approach is problematic, and how to gracefully stop work on the thread when appdomain is unloaded.

I would suggest to use HangFire for such requirements. Its a nice fire and forget engine runs in background, supports different architecture, reliable because it is backed by persistence storage.

You may run tasks in the background and they will complete even after the request ends. Don't let an uncaught exception be thrown. Normally you want to always throw your exceptions. If an exception is thrown in a new thread then it will crash the IIS worker process - w3wp.exe, because you are no longer in the request's context. That's also then going to kill any other background tasks you have running in addition to in-process memory backed sessions if you are using them. This would be hard to diagnose, which is why the practice is discouraged.