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

时间:2015-08-21 17:09:02

标签: 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 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();
            }
        }
    }
}

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

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

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

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

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

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

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

调查结果:

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

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

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:12)

这里有两个不同的概念 -

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

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

    1. 触发IIS管理器手动回收
    2. 从命令提示符调用IIS重置
    3. 在应用程序池上设置空闲超时或关闭时间限制
    4. 当您触摸Web.config,Global.asax或应用程序的bin文件夹中的任何dll时,将重新启动应用程序域。

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

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

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