我可以使用线程在IIS上执行长时间运行的作业吗?
在ASP.Net应用程序中,用户单击网页上的button,然后通过事件处理程序在服务器上实例化一个对象,并在该对象上调用一个方法。 该方法去外部系统做东西,这可能需要一段时间。 所以,我想要做的是在另一个线程中运行该方法调用,所以我可以通过“您的请求已被提交”返回控制权给用户。 我相当高兴能够做到这一点,但是如果用户可以继续查询对象的状态,那将会更好。
我不知道的是,如果IIS允许我的线程继续运行,即使用户会话过期。 试想一下,用户触发事件,并在服务器上实例化对象,并在新线程中激发该方法。 用户对“您的请求已被提交”消息感到满意,并closures浏览器。 最终,这个用户会话将在IIS上超时,但该线程可能仍在运行,正在工作。 IIS允许线程继续运行,还是会在用户会话过期后将其终止并丢弃对象?
编辑:从答案和评论,我明白,这样做的最好方法是移动IIS以外的长时间运行的处理。 除此之外,这涉及到应用程序的回收问题。 实际上,我需要在有限的时间内使版本1开始工作,并且必须在现有的框架内部工作,所以想要避免服务层,因此希望仅仅在IIS内部启动线程。 实际上,这里的“长时间运行”只有几分钟的时间,网站上的并发性会比较低,所以应该没问题。 但是,下一个版本肯定需要分解成一个单独的服务层。
你可以完成你想要的,但这通常是一个坏主意。 有几个ASP.NET博客和CMS引擎采用这种方法,因为他们希望可以在共享主机系统上安装,而不需要依赖于需要安装的Windows服务。 通常他们在应用程序启动时启动Global.asax中的一个长时间运行的线程,并让该线程进程排队等待任务。
除了减lessIIS / ASP.NET可用于处理请求的资源之外,还有在AppDomain被回收时线程被终止的问题,然后您必须在任务正在处理时处理该任务的持久性。以及在AppDomain恢复时启动工作。
请记住,在很多情况下,AppDomain会以默认的时间间隔自动回收,以及更新web.config等。
如果您可以随时处理线程的持久性和事务性方面的问题,那么您可以通过一些外部进程在您的站点上以一定间隔发出请求来解决AppDomain的回收问题 – 这样,如果网站被回收保证在X分钟内自动重启。
再次,这通常是一个坏主意。
编辑:这里有一些这个技巧的例子:
社区服务器:使用Windows服务与后台线程在计划时间间隔运行代码在 网站首次启动时创build后台线程
编辑(从遥远的未来) – 这些天我会用Hangfire 。
我不同意接受的答案。
在ASP.NET中使用后台线程(或任务,以Task.Factory.StartNew
开始)是很好的。 与所有的托pipe环境一样,您可能需要了解并配合pipe理closures的设施。
在ASP.NET中,您可以使用HostingEnvironment.RegisterObject
方法注册需要在closures时正常停止的工作。 看到这篇文章和评论的讨论。
至于你经常听到的一个不好主意的总体主题,考虑部署一个Windows服务(或其他types的额外进程应用程序)的替代scheme:
- Web部署没有更多的微不足道的部署
- 纯粹在Azure网站上不可部署
- 根据后台任务的性质,进程可能需要进行通信。 这意味着某种forms的IPC或服务将不得不访问一个公共数据库。
还要注意的是,一些高级场景可能甚至需要后台线程与请求在相同的地址空间中运行。 我看到了ASP.NET可以做到这一点的事实,因为.NET已经成为可能。
您不希望为此任务使用来自IIS线程池的线程,因为这将使该线程无法处理将来的请求。 你可以看看ASP.NET 2.0中的asynchronous页面 ,但是这确实不是正确的答案。 相反,听起来像你会受益于正在研究微软消息队列 。 从本质上讲,您可以将任务细节添加到队列中,另一个后台进程(可能是Windows服务)将负责执行该任务。 但底线是后台进程完全与IIS隔离。
这里有一个很好的线程和示例代码: http : //forums.asp.net/t/1534903.aspx?PageIndex=2
我甚至玩弄了从线程调用我的网站上的保持活动页面的想法,以帮助保持应用程序池活着。 请记住,如果您使用这种方法,您需要非常好的恢复处理,因为应用程序可以随时回收。 正如许多人所说,如果你有权访问其他服务选项,这是不正确的做法,但对于共享主机,这可能是你唯一的select之一。
为了使应用程序池保持活跃状态,可以在线程正在处理时向自己的站点发出请求。 如果您的进程运行很长时间,这可能有助于保持应用程序池的活跃。
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; }
我们开始走这条路,当我们的应用程序在一台服务器上时,它确实工作正常。 当我们想要扩展到多台机器(或者在一个web garen中使用多个w3wp)时,我们必须重新评估并考虑如何pipe理工作队列,error handling,重试以及正确locking的棘手问题,以确保只有一台机器服务器拿起下一个项目。
…我们意识到我们不是在编写后台处理引擎的业务,所以我们寻找现有的解决scheme,我们登陆了使用真棒的OSS项目hangfire
Sergey Odinokov创造了一个真正简单易用的gem,并允许您将工作的后端换掉,并保持排队。 Hangfire使用后台线程,但保留作业,处理重试,并使您能够看到工作队列。 所以hangfire的工作是健壮的,并生存所有的变化无常的appdomains被回收等
它的基本设置使用sql server作为存储,但是在扩展时可以换出Redis或MSMQ。 它还具有良好的用户界面,可视化所有作业及其状态,并允许您重新排列作业。
我的观点是,虽然完全有可能在后台线程中做你想做的事情,但是为了使其具有可伸缩性和健壮性,还有很多工作要做。 它适用于简单的工作负载,但是当事情变得更复杂时,我更喜欢使用专门构build的库,而不是经过这个努力。
想了解更多关于可用选项的观点,请查看Scott Hanselman的博客 ,该博客介绍了在asp.net中处理后台作业的一些选项。 (他给了一个发光的评论)
也正如John所引用的那样,值得一读Phil Haack的博客 ,了解为什么这个方法有问题,以及如何在appdomain被卸载时正常停止工作。
我会build议使用HangFire这样的要求。 它是一个很好的火,忘记引擎运行在后台,支持不同的架构,可靠,因为它是由持久存储的支持。
你可以创build一个Windows服务来完成这个任务吗? 然后从Web服务器使用.NET远程调用Windows服务来执行操作? 如果那是我所要做的事。
这将消除在IIS上进行中继的需要,并限制其一些处理能力。
如果没有,那么我会强制用户坐在那里,而过程完成。 这样,你确保它完成,而不是被杀死的IIS。
在IIS中似乎有一种支持长时间运行的工作的支持方式。 工作stream服务似乎是为此而devise的,特别是与Windows Server AppFabric结合使用。 该devise支持应用程序池回收,支持自动持久性和恢复长时间运行的工作。
只需创build一个代理进程来运行asynchronous任务; 它不一定是一个Windows服务(尽pipe在大多数情况下,这是最理想的方法.MSMQ是杀死的方式。
您可以在后台运行任务,即使在请求结束后也会完成。 不要让未捕获的exception抛出 。 通常你想要抛出你的例外。 如果在新线程中引发exception,则会导致IIS工作进程崩溃- w3wp.exe ,因为您不再处于请求的上下文中。 如果您正在使用它们,那么除了进程内存支持的会话之外,这也将会消除您正在运行的任何其他后台任务。 这将很难诊断,这就是为什么这种做法是不鼓励的。