定时Job在IIS中潜在危险-IIS 定期回收

引言

有时我们会在IIS中启用一些定时服务,但是你必须清楚IIS会定期回收Asp.net的应用程序的。首先来看IIS啥时候回收APPDomain.

 

APPDomain 回收时机

There are several things that can cause ASP.NET to tear down your AppDomain.

  1. When you modify web.config, ASP.NET will recycle the AppDomain, though the w3wp.exe process (the IIS web server process) stays alive.
  2. IIS will itself recycle the entire w3wp.exe process every 29 hours. It’ll just outright put a cap in the w3wp.exe process and bring down all of the app domains with it.
  3. In a shared hosting environment, many web servers are configured to tear down the application pools after some period of inactivity. For example, if there are no requests to the application within a 20 minute period, it may take down the app domain.

 

If any of these happen in the middle of your code execution, your application/data could be left in a pretty bad state as it’s shut down without warning.

So why isn’t this a problem for your typical per request ASP.NET code?

When ASP.NET tears down the AppDomain, it will attempt to flush the existing requests and give them time to complete before it takes down the App Domain. ASP.NET and IIS are considerate to code that they know is running,such as code that runs as part of a request.

Problem is, ASP.NET doesn’t know about work done on a background thread spawned using a timer or similar mechanism. It only knows about work associated with a request.

有哪些风险

There are three main risks, one of which I’ll focus on in this blog post.

  1. An unhandled exception in a thread not associated with a request will take down the process. This occurs even if you have a handler setup via the Application_Error method. I’ll try and explain why in a follow-up blog post, but this is easy to deal with.
  2. If you run your site in a Web Farm, you could end up with multiple instances of your app that all attempt to run the same task at the same time. A little more challenging to deal with than the first item, but still not too hard. One typical approach is to use a resource common to all the servers, such as the database, as a synchronization mechanism to coordinate tasks.
  3. The AppDomain your site runs in can go down for a number of reasons and take down your background task with it. This could corrupt data if it happens in the middle of your code execution.

告诉ASP.NET ,我还在运行

The good news is there’s an easy way to tell ASP.NET about the work you’re doing! In the System.Web.Hosting namespace, there’s an important class,HostingEnvironment. According to the MSDN docs, this class…

Provides application-management functions and application services to a managed application within its application domain

This class has an important static method, RegisterObject. The MSDN description here isn’t super helpful.

Places an object in the list of registered objects for the application.

For us, what this means is that the RegisterObject method tells ASP.NET that, “Hey! Pay attention to this code here!” Important! This method requires full trust!

This method takes in a single object that implements the IRegisteredObjectinterface. That interface has a single method:

public interface IRegisteredObject
{
    void Stop(bool immediate);
}

 

When ASP.NET tears down the AppDomain, it will first attempt to call Stopmethod on all registered objects.

In most cases, it’ll call this method twice, once with immediate set to false. This gives your code a bit of time to finish what it is doing. ASP.NET gives all instances of IRegisteredObject a total of 30 seconds to complete their work, not 30 seconds each. After that time span, if there are any registered objects left, it will call them again with immediate set to true. This lets you know it means business and you really need to finish up pronto! I modeled my parenting technique after this method when trying to get my kids ready for school.

When ASP.NET calls into this method, your code needs to prevent this method from returning until your work is done. Levi showed me one easy way to do this by simply using a lock. Once the work is done, the code needs to unregister the object.

For example, here’s a simple generic implementation of IRegisteredObject. In this implementation, I simply ignored the immediate flag and try to prevent the method from returning until the work is done. The intent here is I won’t pass in any work that’ll take too long. Hopefully.

public class JobHost : IRegisteredObject
{
    private readonly object _lock = new object();
    private bool _shuttingDown;

    public JobHost()
    {
        HostingEnvironment.RegisterObject(this);
    }

    public void Stop(bool immediate)
    {
        lock (_lock)
        {
            _shuttingDown = true;
        }
        HostingEnvironment.UnregisterObject(this);
    }

    public void DoWork(Action work)
    {
        lock (_lock)
        {
            if (_shuttingDown)
            {
                return;
            }
            work();
        }
    }
}

 

I wanted to get the simplest thing possible working. Note, that when ASP.NET is about to shut down the AppDomain, it will attempt to call the Stop method. That method will try to acquire a lock on the _lock instance. The DoWork method also acquires that same lock. That way, when the DoWork method is doing the work you give it (passed in as a lambda) the Stop method has to wait until the work is done before it can acquire the lock. Nifty.

Later on, I plan to make this more sophisticated by taking advantage of using aTask to represent the work rather than an Action. This would allow me to take advantage of task cancellation instead of the brute force approach with locks.

With this class in place, you can create a timer on Application_Start (I generally use WebActivator to register code that runs on app start) and when it elapses, you call into the DoWork method here. Remember, the timer must be referenced or it could be garbage collected.

Here’s a small example of this:

 

using System;
using System.Threading;
using WebBackgrounder;

[assembly: WebActivator.PreApplicationStartMethod(
  typeof(SampleAspNetTimer), "Start")]

public static class SampleAspNetTimer
{
    private static readonly Timer _timer = new Timer(OnTimerElapsed);
    private static readonly JobHost _jobHost = new JobHost();

    public static void Start()
    {
        _timer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
    }

    private static void OnTimerElapsed(object sender)
    {
        _jobHost.DoWork(() => { /* What is it that you do around here */ });
    }
}

 

建议

This technique can make your background tasks within ASP.NET much more robust. There’s still a chance of problems occurring though. Sometimes, the AppDomain goes down in a more abrupt manner. For example, you might have a blue screen, someone might trip on the plug, or a hard-drive might fail. These catastrophic failures can take down your app in such a way that leaves data in a bad state. But hopefully, these situations occur much less frequently than an AppDomain shutdown.

Many of you might be scratching your head thinking it seems weird to use a web server to perform recurring background tasks. That’s not really what a web server is for. You’re absolutely right. My recommendation is to do one of the following instead:

  • Write a simple console app and schedule it using Windows task schedule.
  • Write a Windows Service to manage your recurring tasks.
  • Use an Azure worker or something similar.

Given that those are my recommendations, why am I still working on a system for scheduling recurring tasks within ASP.NET that handles web farms and AppDomain shutdowns I call WebBackgrounder (NuGet package coming later)?

I mean, besides the fact that I’m thick-headed? Well, for two reasons.

The first is to make development easier. When you get latest from our source code, I just want everything to work. I don’t want you to have to set up a scheduled task, or an Azure worker, or a Windows server on your development box. A development environment can tolerate the issues I described.

The second reason is for simplicity. If you’re ok with the limitations I mentioned, this approach has one less moving part to worry about when setting up a website. There’s no need to configure an external recurring task. It just works.

But mostly, it’s because I like to live life on the edge.

参考

The Dangers of Implementing Recurring Background Tasks In ASP.NET

https://github.com/NuGet/WebBackgrounder

时间: 2024-10-14 12:21:25

定时Job在IIS中潜在危险-IIS 定期回收的相关文章

Quartz.net 定时任务在IIS中未按时执行

IIS 垃圾回收机制下解决Quartz.net 的不执行问题 IIS中涉及了垃圾回收机制,quartz.net 在ASP.NET 项目中可以实现线程监控定时执行任务,但是在IIS7.5机一下版本中涉及到IIS的应用程序池在一段时间后被垃圾回收机制回收,从而导致quartz.net 配置的定时服务无法按时启动.下面来说说如何 本机装的是IIS7.5,默认的垃圾回收时间间隔为1740分钟(=29h),29小时内访问该网站(此处给了我们曲线救国的方针,让我们有机会瞒过IIS的垃圾回收,以时前文提到的问

Quartz.net 定时任务在IIS中没有定时执行

问题:Quartz.net 定时任务在项目部署到IIS中发现没有定时执行 解决方案: 1.在服务器上装一个360(自带自动刷新功能),在工具-->自动刷新-->自动刷新勾上 然后再设置一下自动刷新频率,我这里是设置的1分钟 2.第二种是在IIS上的配置,具体操作如下: 打开IIS,找到网站所在的应用程序池-->高级设置-->找到进程模型下的最大工作进程数设置一下(默认是1,我这里设置的5) 第一次写,其实里面有些原理还是不太清楚,但是效果是有了的哈-_-! 原文地址:https:/

IIS中的上传目录权限设置问题

虽然 Apache 的名声可能比 IIS 好,但我相信用 IIS 来做 Web 服务器的人一定也不少.说实话,我觉得 IIS 还是不错的,尤其是 Windows 2003 的 IIS 6(马上 Longhorn Server 的 IIS 7 也就要来了,相信会更好),性能和稳定性都相当不错.但是我发现许多用 IIS 的人不太会设置 Web 服务器的权限,因此,出现漏洞被人黑掉也就不足为奇了.但我们不应该把这归咎于 IIS 的不安全.如果对站点的每个目录都配以正确的权限,出现漏洞被人黑掉的机会还是

nginx、Apache、IIS中413 Request Entity Too Large问题解决方法

分享下nginx.Apache.IIS三种服务器解决413 Request Entity Too Large问题的方法. 一.nginx服务器 nginx出现这个问题的原因是请求实体太长了.一般出现种情况是Post请求时Body内容Post的数据太大了,如上传大文件过大.POST数据比较多. 处理方法在nginx.conf增加 client_max_body_size的相关设置, 这个值默认是1m,可以增加到8m以增加提高文件大小限制:当然可以设置的更大点.# 在http,server或者loc

在IIS中部署Asp.net Mvc

概述: 最近在做一个MVC 3的项目,在部署服务器时破费了一番功夫,特将过程整理下来,希望可以帮到大家! 本文主要介绍在IIS5.1.IIS6.0.IIS7.5中安装配置MVC 3的具体办法! 正文: IIS5.1 1. 安装Microsoft .net FrameWork 4.0安装包; 2. 安装ASP.NET MVC 3; 3. 在IIS中发布网站,创建虚拟目录,ASP.NET版本选择4.0.30196: 4. 添加MVC的解析: 右击IIS中的虚拟目录选择“属性”-“虚拟目录”-“配置”

快速配置本地IIS中的站点共享到远程访问

前言: 因为各种原因,我们常常要把本机或局域网中搭建的站点发给远方的人访问,他有可能是测试人员.客户.前端.或领导演示,或是内部系统内部论坛临时需要在远程访问,事件变得很麻烦,要么有公网IP,要么能控制路由器做端口映射和动态域名,现在但要一种工具有更简单的办法助你一臂之力. 第一步:搭建测试站点 假设我们在本机或内网的IIS部署了一个内部测试系统:"会员管理系统"端口是8144 <img src="http://www.lantunnel.com/help/imgs/1

IIS中的大文件上传问题解决方法

IIS出于安全考虑限制了大文件的上传,而网上百度到的大部分解决方法都是用一个管理员权限的记事本打开一个文件修改参数,但是我发现里面根本没有网上所说的那些参数,最后自己找到了修改发布文件的webconfig的方法解决的IIS对大文件上传的限制. 首先在system.web中加入以下代码 [csharp] view plain copy <httpRuntime maxRequestLength="2097151"//最大上传长度 useFullyQualifiedRedirectU

在IIS中配置PHP运行环境简单步骤-注意事项

在IIS中配置PHP运行环境简单步骤 安装 IIS 7.0 打开 Control Panel\Programs\Programs and Features\Turn Windows features on or off, 在打开的对话框中勾选上这两个选项: 单击OK等待片刻, IIS就安装完成了. 安装 PHP 5.2.6 1. 下载: PHP 5.2.6 ;  其它版本的 PHP 下载. 2. 将压缩包解压, 放到一个目录中, 比如 D:\php 3. 将 php.ini-dist 复制到 C

Thinkphp3.2在IIS中使用ISAPI_Rewrite去除index.php

步骤: 1.下载ISAPI_Rewrite 下载:http://www.isapirewrite.com/download/isapi_rwl_0055.msi 2.安装ISAPI_Rewrite后,默认是将IIS中所有网站的ISAPI筛选器中都加入了Rewrite,如果服务器上只有一个网站没有问题,如果有多个网站,在指定网站上右键-属性-ISAPI筛选器中加入名称为:Rewrite,地址为:C:\Program Files\Helicon\ISAPI_Rewrite\ISAPI_Rewrite