如何正确使用 IRegisteredObject 阻止应用程序域关闭/回收 Web 应用程序?

Posted

技术标签:

【中文标题】如何正确使用 IRegisteredObject 阻止应用程序域关闭/回收 Web 应用程序?【英文标题】:How to properly use IRegisteredObject to block app domain shutdown / recycle for web app? 【发布时间】:2015-11-15 17:23:02 【问题描述】:

我有一个 .NET MVC Web 应用程序,它需要时间才能正确关闭,因此每当回收 IIS 应用程序域时(即启动一个新实例并接收所有新请求,而旧实例关闭等待未完成的请求完成)我需要阻止此应用程序关闭,直到我的应用程序当前的异步后台工作(不包含未完成的请求)完成。 IRegisteredObject(请参阅http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html)提供了这种阻塞能力,但是,我的进程似乎总是在与我的阻塞时间和 IIS 设置不一致的时候死掉。

我看到了这篇文章 (IRegisteredObject not working as expected),它解释了 IIS 关闭时间限制的重要性,但是,虽然 IRegisteredObject 似乎阻塞了一段时间,但我无法让循​​环阻塞 2 小时的期望时间(我通常也无法根据各种设置获得有意义的结果。

下面是 IRegisteredObject 的一个简单实现,它带有我一直用于测试的后台线程:

public class MyRegisteredObject : IRegisteredObject

    public void Register()
    
        HostingEnvironment.RegisterObject(this);
        Logger.Log("Object has been registered");
    

    // the IRegisteredObject.Stop(...) function gets called on app domain recycle.
    // first, it calls with immediate:false, indicating to shutdown work, then it
    // calls 30s later with immediate:true, and this call 'should' block recycling
    public void Stop(bool immediate)
    
        Logger.Log("App domain stop has been called: " 
            + (immediate ? "Immediate" : "Not Immediate")
            + " Reason: " + HostingEnvironment.ShutdownReason);
        if (immediate)
        
            // block for a super long time
            Thread.Sleep(TimeSpan.FromDays(1));
            Logger.Log("App domain immediate stop finished");
        
    

    // async background task to track if our process is still alive
    public async Task RunInBackgroundAsync()
    
        Logger.Log("Background task started");
        var timeIncrement = TimeSpan.FromSeconds(5);
        var time = TimeSpan.Zero;
        while (time < TimeSpan.FromDays(1))
        
            await Task.Delay(timeIncrement).ConfigureAwait(false);
            time += timeIncrement;
            Logger.Log("Background task running... (" 
                + time.ToString(@"hh\:mm\:ss") + ")");
        
        Logger.Log("Background task finished");
    


public static class Logger

    private static readonly string OutputFilename = @"C:\TestLogs\OutputLog-" + Guid.NewGuid() + ".log";

    public static void Log(string line)
    
        lock (typeof(Logger))
        
            using (var writer = new StreamWriter(OutputFilename, append: true))
            
                writer.WriteLine(DateTime.Now + " - " + line);
                writer.Close();
            
        
    

在应用启动中,我启动了 IRegisteredObject 组件:

var recycleBlocker = new MyRegisteredObject();
recycleBlocker.Register();
var backgroundTask = recycleBlocker.RunInBackgroundAsync();

最后,在测试时,我通过 3 种不同的方式触发了应用程序域回收:

(1) Web.config 文件更改(产生 ConfigurationChange 的 HostingEnvironment.ShutdownReason 值)

(2) 通过点击应用的应用程序池手动回收,然后在 IIS 管理器中回收(产生 HostingEnvironment.ShutdownReason 值 HostingEnvironment)

(3) 允许应用根据进程模型下的 IIS 设置自动回收 - “空闲超时(分钟)”(也产生 HostingEnvironment.ShutdownReason 值 HostingEnvironment)

我没想到会这样,但触发回收的方式似乎起着重要作用...以下是我通过修改回收方式和 IIS 设置(关机限制和空闲时间)的测试结果-out)。

调查结果:

---- Web.config更改回收(ShutdownReason:ConfigurationChange)----

在 IRegisteredObject(immediate: true) 调用发生后,我在日志中看到后台任务的持续时间几乎与为 IIS 空闲超时设置的时间完全相同,而关闭时间限制则不起作用。此外,通过这个循环,假设我将空闲超时设置得足够高,循环阻塞总是受到尊重。通过将空闲超时设置为 0(即关闭),我在一次测试中阻塞了一整天。

---- IIS管理器手动回收(ShutdownReason: HostingEnvironment)----

在 IRegisteredObject(immediate: true) 调用发生后,日志显示与 Web.config 更改完全相反的行为。无论空闲超时是什么,阻塞似乎都不受影响。相反,关闭时间限制指示阻止回收多长时间(直到某一点)。从 1 秒到 5 分钟,循环将根据此关闭限制被阻止。但是,如果将设置设置得更高或关闭,阻塞似乎会停留在大约 5 分钟的上限。

---- 空闲超时自动回收(ShutdownReason: HostingEnvironment)----

终于可以预见了...自动回收实际上是根据空闲超时设置触发的,这会导致类似于手动回收情况的情况:关闭时间限制最多约 5 分钟,但不超过那。想必这是因为自动和手动recycle各有相同的HostingEnvironment.ShutdownReason:HostingEnvironment。

好的......我为这篇文章的长度道歉!如您所见,回收方法和 IIS 设置的组合似乎并没有产生预期的结果。此外,我的目标是能够阻止最多两个小时,这在我在 web.config 回收案例之外的测试中似乎是不可能的,无论我选择什么设置....有人可以请阐明这里的引擎盖下到底发生了什么? ShutdownReason 扮演什么角色?这些 IIS 设置起什么作用?

从根本上说,我在这里遗漏了什么,如何使用 IRegisteredObject 来阻止因自动回收而导致的更长时间?

【问题讨论】:

查看这篇文章:haacked.com/archive/2011/10/16/… 【参考方案1】:

这里有两个不同的概念-

    Application domain Application pool

应用程序域为安全性、可靠性和版本控制以及卸载程序集提供隔离边界。应用程序池定义一组共享一个或多个工作进程的 Web 应用程序。每个应用程序池都可以托管一个或多个应用程序域。

应用程序池可以通过以下方式回收

    触发 IIS 管理器手动回收 从命令提示符调用 IIS 重置 在应用程序池上设置空闲超时或关闭时间限制

当您触摸应用程序的 bin 文件夹中的 Web.config、Global.asax 或任何 dll 时,应用程序域将被回收。

IRegisteredObject 能够拦截应用程序域卸载过程,但它对应用程序池回收没有任何控制权。当触发应用程序池回收时,它会杀死并重新启动 w3wp.exe 进程,并最终杀死与应用程序池关联的所有应用程序域。

这应该可以解释为什么您的 IRegisteredObject 在您触摸 Web.config 时按预期工作,但在您重新循环应用程序池时不会执行预期操作。如果您的空闲超时或关闭超时小于您的 IRegisteredObject 保持应用程序域活动的时间窗口,则在触发应用程序域回收后,IRegisteredObject 将尝试保持应用程序域活动,但是当空闲超时或达到关闭超时,应用程序池将回收,应用程序域将被杀死,无论 IRegisteredObject 是什么。

解决您的问题的方法是关闭应用程序池的空闲超时和关闭时间限制设置,并依靠一些替代方法来回收您的应用程序池。在这种情况下,您的应用程序池将不会自动回收,您可以依靠 IRegisteredObject 来保持应用程序域处于活动状态。

【讨论】:

以上是关于如何正确使用 IRegisteredObject 阻止应用程序域关闭/回收 Web 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确使用 Composer 安装 Laravel 扩展包

如何正确使用 Composer 安装 Laravel 扩展包

如何正确使用 Composer 安装 Laravel 扩展包

如何正确的使用SharedPreferences

如何正确强制正确使用类方法?

如何正确使用 Composer 安装 Laravel 扩展包