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


85

在ASP.Net应用程序中,用户单击网页上的按钮,然后通过事件处理程序在服务器上实例化对象,并在该对象上调用方法。该方法由外部系统处理,这可能需要一段时间。因此,我想做的是在另一个线程中运行该方法调用,以便我可以通过“您的请求已提交”将控制权返回给用户。我很乐意以“一劳永逸”的方式完成此操作,尽管如果用户可以继续轮询对象的状态会更好。

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

编辑:从答案和评论中,我了解做到这一点的最佳方法是将长时间运行的处理移到IIS之外。除了其他方面,这还涉及appdomain回收问题。在实践中,我需要在有限的时间内发布版本1,并且必须在现有的框架内工作,因此想避免使用服务层,因此希望只在IIS内部启动线程。实际上,这里的“长时间运行”仅需几分钟,并且网站上的并发性较低,因此应该可以。但是,下一版本肯定需要拆分为单独的服务层。


1
您是否需要处理什么以及托管环境有更多详细信息?例如,您可以安装服务吗?
senfo

您始终可以使用Async和Await编程方法(Visual Studio 2012+)。比自己管理线程要干净得多。
SausageFingers

Answers:


65

您可以完成所需的操作,但这通常不是一个好主意。几个ASP.NET博客和CMS引擎都采用这种方法,因为它们希望可安装在共享主机系统上,而不依赖于需要安装的Windows服务。通常,当应用程序启动时,它们会在Global.asax中启动一个长期运行的线程,并使该线程进程将任务排队。

除了减少可用于IIS / ASP.NET来处理请求的资源外,还存在回收AppDomain时线程被杀死的问题,然后您必须在进行中时处理任务的持久性,例如以及在AppDomain备份时开始备份工作。

请记住,在许多情况下,AppDomain会以默认间隔自动回收,以及您是否更新了web.config等。

如果您可以随时处理被杀死的线程的持久性和事务性问题,那么可以通过一些外部进程在一定时间间隔内对您的站点进行请求来避免AppDomain的回收-因此,如果该站点被回收,则可以保证它会在X分钟内自动重新启动。

同样,这通常是一个坏主意。

编辑:以下是此技术在操作中的一些示例:

社区服务器:使用Windows服务与后台线程以预定的间隔运行代码在 网站首次启动时创建后台线程

编辑(来自遥远的未来)-这些天,我将使用Hangfire


1
谢谢,这很有见地。回收是绝对的杀手,我从您所说的中得出结论,我可以像一成不变的事情做到这一点,但这是危险的。
弗朗斯,

是的,请在此处检查以下内容: professionalaspnet.com/archive/2007/09/02/… 这是有关COmmunity Server如何处理同一事物的主题: dev.communityserver.com/forums/t/459942.aspx
Bryan Batchelder

就我而言,我能够告诉IIS不要回收再利用,也不要让网站闲置。这样一来,我便可以在此假设下工作,并且仅在部署时以及计划进行维护时才对站点进行回收。由于我只是做一个管理员类型的网站,因此对我来说非常理想。
布雷特

1
奇怪的是,这有这么多的反对意见。这是可能的,这是ASP.NET的非常酷的事情之一:所有请求与您可以创建的任何后台线程一起在同一个AppDomain中进行服务。给定的理由为什么这不是一个“坏主意”,我都没有说服力。是的,AppDomains可能会崩溃-但这并不是ASP.NET环境唯一的。您需要与您的托管环境(Google的HostingEnvironment.RegisterObject)玩得很好。
约翰

3
.NET 4.5.2添加了一项新功能,QueueBackgroundWorkItem可以轻松注册后台任务,您可能需要再次更新答案。
Scott Chamberlain'7

39

我不同意接受的答案。

Task.Factory.StartNew在ASP.NET中可以使用后台线程(或以开头的任务)。与所有托管环境一样,您可能希望了解管理关机的设施并与之合作。

在ASP.NET中,您可以使用该HostingEnvironment.RegisterObject方法注册需要在关机时正常停止的工作。请参阅本文和评论以进行讨论。

(正如Gerard在他的评论中指出的那样,现在还HostingEnvironment.QueueBackgroundWorkItem需要调用RegisterObject来注册一个调度程序以供后台项目使用。总体而言,新方法更好,因为它基于任务。)

至于您经常听到的一般主题是个坏主意,请考虑部署Windows服务(或另一种额外的进程应用程序)的替代方法:

  • Web部署不再需要琐碎的部署
  • 不能仅在Azure网站上部署
  • 根据后台任务的性质,流程可能必须进行通信。这意味着某种形式的IPC或服务将必须访问公共数据库。

还要注意,某些高级方案甚至可能需要后台线程在与请求相同的地址空间中运行。我看到一个事实,即ASP.NET可以做到这一点,这是通过.NET实现的巨大优势。


这很棒。我的任务需要大约30秒才能完成。如果您只需要延迟应用程序池足够长的时间来完成工作,则是完美的选择。
史蒂夫·史蒂夫

1
从.Net Framework 4.5.2开始,您还可以使用HostingEnvironment.QueueBackgroundWorkItem(Action <CancellationToken>)
GerardCarbó19年

根据我的观察,似乎IIS最终关闭了我正在运行的ASP.NET Web API,直到有新请求出现时才启动它。因此,在预定的时间,我的线程应该启动应用程序,甚至没有运行。我对此有错吗?
David Sopko '19

7

您不想使用IIS线程池中的线程来执行此任务,因为它将使该线程无法处理将来的请求。您可以研究ASP.NET 2.0中的“异步页面”,但这也不是正确的答案。相反,听起来像您将受益的是研究Microsoft消息队列。本质上,您将任务详细信息添加到队列中,并且另一个后台进程(可能是Windows Service)将负责执行该任务。但最重要的是,后台进程与IIS完全隔离。


嗯,MSMQ-我长期喜欢的技术之一。感谢您的见解和链接。
弗朗斯

6

我建议使用HangFire满足此类要求。它是一个很好的即发即弃引擎,在后台运行,支持不同的体系结构,可靠,因为它由持久性存储支持。


5

这里有一个很好的线程和示例代码: http //forums.asp.net/t/1534903.aspx?PageIndex=2

我什至对从线程调用我的网站上的“保持活动”页面以保持应用程序池保持活动状态的想法表示怀疑。请记住,如果您使用此方法,则需要非常好的恢复处理,因为该应用程序可能会随时回收。正如许多人提到的那样,如果您可以访问其他服务选项,那么这不是正确的方法,但是对于共享主机,这可能是您唯一的选择之一。

为了使应用程序池保持活动状态,您可以在线程正在处理时向自己的站点发出请求。如果您的进程运行时间较长,这可能有助于保持应用程序池的活动状态。

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;
    }

5

我们从这条道路开始,当我们的应用程序在一台服务器上时,它实际上可以正常工作。当我们想扩展到多台机器(或在一个Web Garen中使用多个w3wp)时,我们不得不重新评估并研究如何管理工作队列,错误处理,重试以及正确锁定以确保只有一台的棘手问题。服务器提取下一个项目。

...我们意识到我们不从事编写后台处理引擎的业务,因此我们寻找现有的解决方案,并使用了出色的OSS项目 hangfire

谢尔盖·奥迪诺科夫(Sergey Odinokov)创建了一个真正的宝石,它非常容易上手,并且允许您交换工作持久化和排队的后端。Hangfire使用后台线程,但可以保留作业,处理重试并为您提供工作队列的可见性。因此,hangfire的工作非常强大,可以应对所有反复使用的appdomain的变化。

它的基本设置使用sql server作为存储,但是您可以在需要扩展时换成Redis或MSMQ。它还具有出色的UI,可用于可视化所有作业及其状态,并允许您重新排队作业。

我的观点是,尽管完全有可能在后台线程中完成您想做的事情,但要使它具有可伸缩性和健壮性,还有很多工作要做。对于简单的工作负载来说很好,但是当事情变得更复杂时,我更喜欢使用专门构建的库,而不是为此付出努力。

有关可用选项的更多信息,请查看Scott Hanselman的博客,该介绍了处理asp.net中的后台作业的一些选项。(他给Hangfire进行了热烈的审查)

正如John所引用的那样,值得阅读Phil Haack的博客,其中介绍了该方法为何存在问题以及如何在卸载appdomain时正常停止线程上的工作。


2

您可以创建Windows服务来执行该任务吗?然后从Web服务器使用.NET远程处理来调用Windows服务来执行该操作?如果是这种情况,那就是我会做的。

这将消除在IIS上进行中继的需求,并限制其某些处理能力。

如果没有,那么我将迫使用户坐在该过程中。这样,您可以确保它已完成并且不会被IIS杀死。


2

在IIS中,似乎确实存在一种支持长期运行的工作的受支持方法。 Workflow Services似乎是为此目的而设计的,尤其是与Windows Server AppFabric结合使用时。该设计支持自动持久性和恢复长期运行的工作,从而可以回收应用程序池。


2

您可以在后台运行任务,即使请求结束后它们也将完成。不要让未捕获的异常抛出。通常,您总是想抛出异常。如果在新线程中引发异常,则它将使IIS工作进程w3wp.exe崩溃,因为您不再处于请求的上下文中。如果您正在使用进程内内存支持的会话,那么这还将杀死您正在运行的任何其他后台任务。这将很难诊断,因此不鼓励这样做。


1

只需创建一个代理进程即可运行异步任务;它不一定非要是Windows服务(尽管在大多数情况下这是更理想的方法。MSMQ可以解决问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.