在ASP.Net应用程序中,用户单击网页上的按钮,然后通过事件处理程序在服务器上实例化对象,并调用该对象上的方法.该方法转到外部系统来做东西,这可能需要一段时间.所以,我想要做的是在另一个线程中运行该方法调用,以便我可以通过"您的请求已经提交"将控制权返回给用户.我很乐意这样做,虽然用户可以继续轮询对象的状态,但它会更好.
我不知道的是,即使用户会话过期,IIS是否允许我的线程继续运行.想象一下,用户触发事件并在服务器上实例化对象并在新线程中触发该方法.用户对"您的请求已提交"消息感到满意并关闭其浏览器.最终,此用户会话将在IIS上超时,但该线程可能仍在运行,正在运行.IIS会允许线程继续运行还是会在用户会话到期后将其终止并处理掉对象?
编辑:从答案和评论,我知道这样做的最好方法是将长时间运行的处理移到IIS之外.除了其他一切,这涉及appdomain回收问题.在实践中,我需要在有限的时间内完成版本1并且必须在现有框架内工作,因此希望避免服务层,因此希望在IIS内部启动线程.实际上,这里的"长时间运行"只需几分钟,网站上的并发性很低,所以应该没问题.但是,下一个版本肯定需要拆分成一个单独的服务层.
你可以完成你想要的,但这通常是一个坏主意.一些ASP.NET博客和CMS引擎采用这种方法,因为它们希望可以安装在共享主机系统上,而不依赖于需要安装的Windows服务.通常,当应用程序启动时,它们会在Global.asax中启动一个长时间运行的线程,并让该线程进程排队等待任务.
除了减少IIS/ASP.NET可用于处理请求的资源之外,还有问题,当AppDomain被回收时,线程被杀死,然后你必须处理任务在飞行中的持久性,如以及当AppDomain重新启动时启动工作.
请记住,在许多情况下,AppDomain会以默认间隔自动回收,以及更新web.config等.
如果您可以随时处理线程的持久性和事务性方面的处理,那么您可以通过让某些外部进程在某个时间间隔内对您的站点发出请求来绕过AppDomain回收 - 这样,如果该站点被回收,保证在X分钟内自动重新启动.
同样,这通常是一个坏主意.
编辑:以下是此技术的一些实例:
社区服务器:使用Windows服务与后台线程以预定间隔运行代码在 网站首次启动时创建后台线程
编辑(来自遥远的未来) - 这些天我会使用Hangfire.
我不同意接受的答案.
Task.Factory.StartNew
在ASP.NET中使用后台线程(或以任务开头)很好.与所有托管环境一样,您可能希望了解并配合管理关闭的设施.
在ASP.NET中,您可以使用该HostingEnvironment.RegisterObject
方法注册需要在关闭时正常停止的工作.请参阅此文章和讨论的评论.
(正如Gerard在他的评论中指出的那样,现在还要HostingEnvironment.QueueBackgroundWorkItem
调用RegisterObject
注册后台项目的调度程序.总体而言,新方法更好,因为它是基于任务的.)
至于您经常听到的一般主题是一个坏主意,请考虑部署Windows服务(或其他类型的额外进程应用程序)的替代方案:
Web部署不再需要进行简单的部署
不能完全部署在Azure网站上
根据后台任务的性质,进程可能需要进行通信.这意味着某种形式的IPC或服务必须访问公共数据库.
另请注意,某些高级方案甚至可能需要后台线程在与请求相同的地址空间中运行.我看到ASP.NET可以通过.NET实现这一优势.
您不希望使用IIS线程池中的线程执行此任务,因为这会使该线程无法处理将来的请求.您可以查看ASP.NET 2.0中的异步页面,但这也不是正确的答案.相反,听起来你会受益的是调查Microsoft消息队列.基本上,您可以将任务详细信息添加到队列中,另一个后台进程(可能是Windows服务)将负责执行该任务.但底线是后台进程与IIS完全隔离.
我建议使用HangFire来满足这些要求.它是一个很好的火和忘记引擎在后台运行,支持不同的架构,可靠,因为它由持久性存储支持.
这里有一个很好的线程和示例代码: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; }
我们从这条道路开始,当我们的应用程序在一台服务器上时,它实际上可以正常工作。当我们想扩展到多台机器(或在Web Garen中使用多个w3wp)时,我们不得不重新评估并研究如何管理工作队列,错误处理,重试以及正确锁定以确保只有一台的棘手问题。服务器提取下一个项目。
...我们意识到我们不从事编写后台处理引擎的业务,因此我们寻找了现有的解决方案,并使用了令人敬畏的OSS项目hangfire
谢尔盖·奥迪诺科夫(Sergey Odinokov)创建了一个真正的宝石,它非常容易上手,并且允许您交换工作持久化和排队的后端。Hangfire使用后台线程,但可以保留作业,处理重试并为您提供工作队列的可见性。因此,hangfire的工作是强大的,并且可以承受被回收的appdomain的各种变化。
它的基本设置使用sql server作为存储,但是您可以在需要扩展时换成Redis或MSMQ。它还具有出色的UI,可用于可视化所有作业及其状态,并允许您重新排队作业。
我的观点是,尽管完全有可能在后台线程中完成您想做的事情,但要使它具有可伸缩性和健壮性,还有很多工作要做。对于简单的工作负载而言,这很好,但是当事情变得更加复杂时,我更喜欢使用专门构建的库,而不是为此付出努力。
有关可用选项的更多信息,请查看Scott Hanselman的博客,其中涵盖了处理asp.net中的后台作业的一些选项。(他给Hangfire进行了热烈的审查)
正如John所引用的那样,值得阅读Phil Haack的博客,其中介绍了该方法为何存在问题以及如何在卸载appdomain时正常停止线程上的工作。