【问题标题】:Can I use threads to carry out long-running jobs on IIS?我可以使用线程在 IIS 上执行长时间运行的作业吗?
【发布时间】:2010-10-06 21:44:41
【问题描述】:

在 ASP.Net 应用程序中,用户单击网页上的按钮,然后通过事件处理程序实例化服务器上​​的对象并调用对象上的方法。 该方法转到外部系统来做一些事情,这可能需要一段时间。所以,我想做的是在另一个线程中运行该方法调用,这样我就可以通过“您的请求已提交”将控制权返回给用户。 我很高兴能做到即发即弃,但如果用户可以继续轮询对象的状态会更好。

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

编辑:从答案和 cmets 中,我了解到最好的方法是将长期运行的处理移到 IIS 之外。除此之外,这还涉及 appdomain 回收问题。在实践中,我需要在有限的时间内启动版本 1,并且必须在现有框架内工作,所以想避开服务层,因此希望只在 IIS 内启动线程。在实践中,这里的“长时间运行”只会是几分钟,网站上的并发会很低,所以应该没问题。但是,下一个版本肯定需要拆分成一个单独的服务层。

【问题讨论】:

  • 您对需要处理的内容和您的托管环境有更多详细信息吗?例如,您是否能够安装服务?
  • 您始终可以使用Async and Await 编程方法(Visual Studio 2012+)。比自己管理线程要干净得多。

标签: asp.net multithreading iis background-process


【解决方案1】:

你可以完成你想要的,但这通常是个坏主意。一些 ASP.NET 博客和 CMS 引擎采用这种方法,因为它们希望可安装在共享主机系统上,而不依赖于需要安装的 Windows 服务。通常,当应用启动时,它们会在 Global.asax 中启动一个长时间运行的线程,并让该线程处理将任务排队。

除了减少 IIS/ASP.NET 可用于处理请求的资源之外,您还遇到了在 AppDomain 被回收时线程被杀死的问题,然后您必须处理任务在执行过程中的持久性——飞行,以及在 AppDomain 恢复时开始备份工作。

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

如果您可以随时处理线程被终止的持久性和事务性方面,那么您可以通过一些外部进程每隔一段时间在您的站点上发出请求来绕过 AppDomain 回收 - 这样如果站点被回收,您保证在 X 分钟内自动重新启动。

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

编辑:以下是该技术的一些实际应用示例:

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

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

【讨论】:

  • 谢谢,这很有见地。回收是一个绝对的杀手,我从你所说的中得到,我可以做到这一点,这是一件艰难而快速的事情,但它是危险的。
  • 是的,在这里检查一下:professionalaspnet.com/archive/2007/09/02/… 这是一个关于社区服务器如何处理同样事情的线程:dev.communityserver.com/forums/t/459942.aspx
  • 在我的例子中,我能够告诉 IIS 永远不要回收并且永远不要让站点闲置。这使得我能够在这个假设下工作,并且站点只会在部署时和计划维护发生时被回收。因为我只是在做一个管理员类型的网站,所以这对我来说是完美的。
  • 奇怪的是,这有这么多的赞成票。这件事是可能的,这是关于 ASP.NET 的非常酷的事情之一:所有请求都在同一个 AppDomain 以及您可以创建的任何后台线程中提供服务。为什么这是一个“坏主意”的给定原因都没有让我信服。 AppDomains 可以关闭,是的 - 但这并不是 ASP.NET 环境所独有的。您需要很好地使用您的托管环境(google for HostingEnvironment.RegisterObject)。
  • .NET 4.5.2 新增功能QueueBackgroundWorkItem 可以轻松注册后台任务,您可能需要再次更新答案。
【解决方案2】:

我不同意接受的答案。

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

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

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

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

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

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

【讨论】:

  • 这很棒。我有一个任务需要大约 30 秒才能完成。如果您只需要将应用程序池延迟足够长的时间以完成工作,那就完美了。
  • 从 .Net Framework 4.5.2 你也可以使用 HostingEnvironment.QueueBackgroundWorkItem(Action)
  • 在我的观察中,似乎 IIS 最终会关闭我正在运行的 ASP.NET Web API,并且在收到新请求之前不会重新启动它。所以在我的线程的指定时间应该启动应用程序甚至可能没有运行。我错了吗?
【解决方案3】:

您不希望将 IIS 线程池中的线程用于此任务,因为这会使该线程无法处理未来的请求。您可以查看Asynchronous Pages in ASP.NET 2.0,但这也不是正确的答案。相反,听起来你会从中受益的是查看Microsoft Message Queuing。本质上,您会将任务详细信息添加到队列中,而另一个后台进程(可能是 Windows 服务)将负责执行该任务。但底线是后台进程与 IIS 完全隔离。

【讨论】:

  • 啊,MSMQ - 我长期喜爱的技术之一。感谢您的洞察力和链接。
【解决方案4】:

我建议使用HangFire 来满足此类要求。它是一个很好的fire and forget引擎,在后台运行,支持不同的架构,可靠,因为它有持久性存储支持。

【讨论】:

    【解决方案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;
        }
    

    【讨论】:

      【解决方案6】:

      我们从这条路开始,当我们的应用在一台服务器上时,它实际上运行良好。当我们想要扩展到多台机器(或在 Web garen 中使用多个 w3wp)时,我们必须重新评估并研究如何管理工作队列、错误处理、重试以及正确锁定以确保只有一个的棘手问题服务器拿起下一个项目。

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

      Sergey Odinokov 创建了一个真正的 gem,它非常容易上手,并允许您更换工作保持和排队的后端。 Hangfire 使用后台线程,但保留作业、处理重试并让您查看工作队列。因此,hangfire 作业很健壮,并且可以在所有变幻莫测的 appdomains 被回收等情况下生存下来。

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

      我的观点是,虽然它完全有可能在后台线程中做你想做的事,但要使其具有可扩展性和健壮性还有很多工作要做。它适用于简单的工作负载,但当事情变得更复杂时,我更喜欢使用专门构建的库而不是通过这项工作。

      有关可用选项的更多观点,请查看 Scott Hanselman 的 blog,其中涵盖了在 asp.net 中处理后台作业的一些选项。 (他给了hangfire一个热情洋溢的评论)

      正如 John 所引用的,值得一读 Phil Haack 的 blog,了解该方法为何存在问题,以及如何在卸载 appdomain 时优雅地停止线程上的工作。

      【讨论】:

        【解决方案7】:

        你能创建一个 Windows 服务来完成这项任务吗?然后使用 .NET remoting 从 Web Server 调用 Windows Service 来执行操作?如果是这样的话,我会这样做。

        这将消除在 IIS 上中继的需要,并限制其部分处理能力。

        如果不是,那么我会在该过程完成时强制用户坐在那里。这样可以确保它已完成并且不会被 IIS 杀死。

        【讨论】:

          【解决方案8】:

          似乎有一种受支持的方式可以在 IIS 中托管长期运行的工作。 Workflow Services 似乎是为此而设计的,尤其是与 Windows Server AppFabric 结合使用。该设计通过支持自动持久化和恢复长期运行的工作,允许应用程序池回收。

          【讨论】:

            【解决方案9】:

            您可以在后台运行任务,即使在请求结束后它们也会完成。 不要抛出未捕获的异常。通常,您希望始终抛出异常。如果在新线程中抛出异常,那么它将使IIS worker process - w3wp.exe 崩溃,因为您不再处于请求的上下文中。如果您正在使用它们,那么除了进程内内存支持的会话之外,这还将杀死您正在运行的任何其他后台任务。这很难诊断,这就是不鼓励这种做法的原因。

            【讨论】:

              【解决方案10】:

              只需创建一个代理进程来运行异步任务;它不一定是 Windows 服务(尽管在大多数情况下这是更理想的方法。MSMQ 已经过时了。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多