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

Rya*_*yan 10 c# asp.net iis appdomain recycle

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

我看到这篇文章(IRegisteredObject没有按预期工作)解释了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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在app start中,我启动了IRegisteredObject组件:

var recycleBlocker = new MyRegisteredObject();
recycleBlocker.Register();
var backgroundTask = recycleBlocker.RunInBackgroundAsync();
Run Code Online (Sandbox Code Playgroud)

最后,在测试时,我通过3种不同的方式引发了app域回收:

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

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

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

我不会想到这一点,但是回收触发的方式似乎发挥了巨大的作用......以下是我通过测试的结果,我修改了回收方式和IIS设置(关机限制和空闲超时) .

发现:

---- Web.config change recycle(ShutdownReason:ConfigurationChange)----

在IRegisteredObject(立即:true)调用发生后,我在日志中看到后台任务几乎完全停留在IIS空闲超时时间,而关机时间限制不起任何作用.此外,通过此循环,假设我将空闲超时设置得足够高,则始终遵循循环阻塞.我通过将空闲超时设置为0(即关闭)在一次测试中阻止了一整天.

---- IIS管理器手动循环(ShutdownReason:HostingEnvironment)----

发生IRegisteredObject(立即:true)调用后,与Web.config更改相比,日志显示完全相反的行为.无论空闲超时是什么,阻塞似乎都不会受到影响.相反,关闭时间限制决定了阻止循环的时间(最多到一个点).从1秒到5分钟,将根据此关闭限制阻止回收.但是,如果将设置设置得更高或关闭,则堵塞似乎保持在大约5分钟的上限.

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

最后可预测的事情......自动循环实际上会根据空闲超时设置触发,然后导致类似于手动循环情况的情况:关闭时间限制最多约5分钟但不超过此时间.据推测这是因为自动和手动回收每个具有相同的HostingEnvironment.ShutdownReason:HostingEnvironment.

好的...我为此长度道歉!如您所见,循环方法和IIS设置的组合似乎不会产生预期的结果.此外,我这一切的目标是能够阻止最多两个小时,这似乎不可能从我在web.config回收案例之外的测试,无论我选择的设置....可以有人请揭示引擎盖下究竟发生了什么?ShutdownReason扮演什么角色?这些IIS设置扮演什么角色?

从根本上说,我在这里缺少什么,以及如何使用IRegisteredObject来阻止因自动循环而导致的更长时间?

Kni*_*Fox 12

这里有两个不同的概念 -

  1. 应用领域
  2. 应用程序池

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

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

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

当您触摸Web.config,Global.asax或应用程序的bin文件夹中的任何dll时,将重新计算应用程序域.

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

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

解决问题的方法是关闭应用程序池的空闲超时和关闭时间限制设置,并依赖某种备用方法来回收应用程序池.在这种情况下,您的应用程序池不会自动回收,您可以依赖IRegisteredObject来保持应用程序域的活动.