如何正确使用 IRegisteredObject 来阻止应用程序域关闭/回收 Web 应用程序?
How to properly use IRegisteredObject to block app domain shutdown / recycle for web app?
我有一个 .NET MVC Web 应用程序,它需要时间才能正确关闭,因此每当回收 IIS 应用程序域时(即,新实例启动并接收所有新请求,而旧实例关闭等待未完成的请求完成)我需要阻止此应用程序关闭,直到我的应用程序当前的异步后台工作(不包含未完成的请求)完成。 IRegisteredObject(参见 http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html)提供了这种阻塞能力,但是,我的进程有时似乎总是死掉,这与我的阻塞时间和 IIS 设置不一致。
我看到这个 post (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 的 HostingEnvironment.ShutdownReason 值)
(3) 允许应用根据进程模型下的 IIS 设置自动回收 - "Idle Time-out (minutes)"(还会产生 HostingEnvironment 的 HostingEnvironment.ShutdownReason 值)
我没想到会这样,但是触发回收的方式似乎起到了很大的作用...以下是我通过测试修改回收方式和 IIS 设置(关机限制和空闲超时)。
调查结果:
----Web.config更改回收(ShutdownReason:ConfigurationChange)----
调用 IRegisteredObject(immediate: 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 来阻止由自动回收引起的更长时间?
这里有两个不同的概念-
应用程序域为安全性、可靠性和版本控制以及卸载程序集提供隔离边界。应用程序池定义一组共享一个或多个工作进程的 Web 应用程序。每个应用程序池都可以托管一个或多个应用程序域。
应用程序池可以通过以下方式回收
- 正在触发 IIS 管理器手动回收
- 从命令提示符调用 IIS 重置
- 在您的应用程序池上设置空闲超时或关闭时间限制
当您触摸 Web.config、Global.asax 或应用程序 bin 文件夹中的任何 dll 时,应用程序域将被回收。
IRegisteredObject 能够拦截应用程序域卸载的过程,但是,它对应用程序池回收没有任何控制.触发应用程序池回收时,它会终止并重新启动 w3wp.exe 进程,并最终终止与应用程序池关联的所有应用程序域。
这应该可以解释为什么您的 IRegisteredObject 在您触摸 Web.config 时按预期工作,但在您回收应用程序池时不会执行预期的操作。如果您的空闲超时或关闭超时小于您的 IRegisteredObject 保持应用程序域活动的时间 window,则在触发应用程序域回收后,IRegisteredObject 将尝试保持应用程序域活动但是当空闲时-超时或关闭超时,应用程序池将回收,应用程序域将被终止,无论 IRegisteredObject。
您的问题的一个解决方案是关闭应用程序池的空闲超时和关闭时间限制设置,并依靠一些替代方法来回收您的应用程序池。在这种情况下,您的应用程序池将不会自动回收,您可以依靠 IRegisteredObject 来保持应用程序域的活动。
我有一个 .NET MVC Web 应用程序,它需要时间才能正确关闭,因此每当回收 IIS 应用程序域时(即,新实例启动并接收所有新请求,而旧实例关闭等待未完成的请求完成)我需要阻止此应用程序关闭,直到我的应用程序当前的异步后台工作(不包含未完成的请求)完成。 IRegisteredObject(参见 http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html)提供了这种阻塞能力,但是,我的进程有时似乎总是死掉,这与我的阻塞时间和 IIS 设置不一致。
我看到这个 post (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 的 HostingEnvironment.ShutdownReason 值)
(3) 允许应用根据进程模型下的 IIS 设置自动回收 - "Idle Time-out (minutes)"(还会产生 HostingEnvironment 的 HostingEnvironment.ShutdownReason 值)
我没想到会这样,但是触发回收的方式似乎起到了很大的作用...以下是我通过测试修改回收方式和 IIS 设置(关机限制和空闲超时)。
调查结果:
----Web.config更改回收(ShutdownReason:ConfigurationChange)----
调用 IRegisteredObject(immediate: 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 来阻止由自动回收引起的更长时间?
这里有两个不同的概念-
应用程序域为安全性、可靠性和版本控制以及卸载程序集提供隔离边界。应用程序池定义一组共享一个或多个工作进程的 Web 应用程序。每个应用程序池都可以托管一个或多个应用程序域。
应用程序池可以通过以下方式回收
- 正在触发 IIS 管理器手动回收
- 从命令提示符调用 IIS 重置
- 在您的应用程序池上设置空闲超时或关闭时间限制
当您触摸 Web.config、Global.asax 或应用程序 bin 文件夹中的任何 dll 时,应用程序域将被回收。
IRegisteredObject 能够拦截应用程序域卸载的过程,但是,它对应用程序池回收没有任何控制.触发应用程序池回收时,它会终止并重新启动 w3wp.exe 进程,并最终终止与应用程序池关联的所有应用程序域。
这应该可以解释为什么您的 IRegisteredObject 在您触摸 Web.config 时按预期工作,但在您回收应用程序池时不会执行预期的操作。如果您的空闲超时或关闭超时小于您的 IRegisteredObject 保持应用程序域活动的时间 window,则在触发应用程序域回收后,IRegisteredObject 将尝试保持应用程序域活动但是当空闲时-超时或关闭超时,应用程序池将回收,应用程序域将被终止,无论 IRegisteredObject。
您的问题的一个解决方案是关闭应用程序池的空闲超时和关闭时间限制设置,并依靠一些替代方法来回收您的应用程序池。在这种情况下,您的应用程序池将不会自动回收,您可以依靠 IRegisteredObject 来保持应用程序域的活动。