受欢迎的博客标签

保持应用程序在激活状态-1

Published
Forcing an ASP.NET Application to 'stay alive' http://weblog.west-wind.com/posts/2007/May/10/Forcing-an-ASPNET-Application-to-stay-alive This post in the 'stupid pet tricks' category I suppose - it's not one of the things that you need to do every day, but if you need to run an application that requires that your ASP.NET application stays up and running without the discontinuity of AppDomain or worker process shutdowns this can be useful. First some background on the scenario. I'm working on a small project for a customer that deals with a background scheduler that's running continuously and checks the contents of a POP3 box at intervals. The scheduler itself is created on a new, separate thread that then keeps running in the background. The thread runs in an endless loop with an AutoResetEvent to control the check frequency and active status. Now your (and mine too) first thought might be - a scheduler has no business in ASP.NET, write a Windows Service or something else that runs permanently on a machine. However, in this particular application the requirement was that it had to run within the confines of a Web server due to the hosting requirements - the application couldn't be hosted locally and it has to be - for the time being - run on an ISP box. So the question then is: How do you create a process that runs as 'always' active in ASP.NET? There a few ways, but the easiest I've found is to just spin up a separate thread that runs in an endless loop with some sort of exit flag condition and so keeps the thread alive and running indefinitely for the lifetime of the application. A scheduler like this is simple enough to create: ///<summary> /// This is the manager class that handles running the email operation on a background thread ///</summary> public class Scheduler : IDisposable {     ///<summary>     /// Determines the status fo the Scheduler     ///</summary>            public bool Cancelled     {         get { return _Cancelled; }         set { _Cancelled = value; }     }     private bool _Cancelled = false;         ///<summary>     /// The frequency of checks against hte POP3 box are     /// performed in Seconds.     ///</summary>     private int CheckFrequency = 180;       EmailForwarder Forwarder = new EmailForwarder();       AutoResetEvent WaitHandle = new AutoResetEvent(false);       object SyncLock = new Object();       public Scheduler()     {       }       ///<summary>     /// Starts the background thread processing           ///</summary>     ///<param name="CheckFrequency">Frequency that checks are performed in seconds</param>     public void Start(int checkFrequency)     {         this.Forwarder.DeleteMessages = App.Configuration.DeletePop3Messages;                      // *** Ensure that any waiting instances are shut down         //this.WaitHandle.Set();           this.CheckFrequency = checkFrequency;         this.Cancelled = false;           Thread t = new Thread(Run);         t.Start();     }       ///<summary>     /// Causes the processing to stop. If the operation is still     /// active it will stop after the current message processing     /// completes     ///</summary>     public void Stop()     {         lock (this.SyncLock)         {             if (Cancelled)                 return;               this.Cancelled = true;             this.WaitHandle.Set();         }     }       ///<summary>     /// Runs the actual processing loop by checking the mail box     ///</summary>     private void Run()     {         this.Forwarder.LogMessage("...Starting Service",true);           // *** Start out  waiting         this.WaitHandle.WaitOne(this.CheckFrequency * 1000, true);           while (!Cancelled)         {             if (App.Configuration.LogDetail)                 this.Forwarder.LogMessage("...Checking mailbox...", true);               if (!this.Forwarder.ProcessMessages())                 this.Forwarder.LogMessage("...Processing failed: " + Forwarder.ErrorMessage, false);             else             {                 if (App.Configuration.LogDetail)                     this.Forwarder.LogMessage("...Processing completed", true);             }               // *** Http Ping to force the server to stay alive             this.PingServer();               // *** Put in             this.WaitHandle.WaitOne(this.CheckFrequency * 1000,true);           }           this.Forwarder.LogMessage("...Shutting down service", true);     }       public void PingServer()     {         try         {             WebClient http = new WebClient();             string Result = http.DownloadString(App.Configuration.PingUrl);         }         catch (Exception ex)         {             string Message = ex.Message;         }     }         #region IDisposable Members       public void Dispose()     {         this.Stop();     }       #endregion } This is a slightly specific implementation, but the but the overall concept can be used with most applications with slight changes in the Run methods implementation (heck I should have made this generic and created a DoProcessing() method to override). Basically there are a start and stop methods that check a Cancelled flag. If the cancelled flag is set to true the Run method's loop terminates and the separately spun up thread is terminated. But while Cancelled is not set, the thread keeps looping and running in the background doing its thing at the specified CheckFrequency interval. One important aspect of this scenario is where to hook up the instantiation of this class - you'll want to this to happen only once for the given application. A good place for this is Application_Start (in global.asax or in a custom module) or alternately in a static constructor of a property that is sure to get fired in the course of the application on every hit. In global.asax the code looks like this: void Application_Start(object sender, EventArgs e) {      App.Scheduler = new EmailForwarding.Scheduler();     App.Scheduler.Start(App.Configuration.CheckFrequency);  } App.Scheduler is a static property on an App class I create for every project. My App class contains basic Application settings and constants as well as any static instances of objects that through this mechanism become easily and globally accessible in the application. Another class I always have on my App class for example is App.Configuration which contains all the configuration settings (which also persist into Web.config or an external store). Application Lifetime So the above code accounts for starting a background thread and keeping it running, and it will keep running until the application shuts down. But there's are two problems here if this scheduler is meant as an always on type of service: If the Application shuts down for any reason the scheduler shuts down with it Nothing happens until the application was hit at least once The former is obvious enough, but it's more common than you might think. ASP.NET applications can recyle to a number of different reasons including, IIS Worker Process idle timeout shutdowns, any configuration change in web.config, any code updates (either a new BIN directory DLL, or any code updates in APP_CODE in web projects), IIS Health Monitoring deciding to recyle the worker process,  or more drastic things like Web Server or IIS restarts. It happens more frequently than you might think. The first thing to deal with is idle time timeouts which can be set for IIS and which cause IIS to recycle the Application if no activity occurs against it. You can get around this issue by pinging the server with an HTTP hit within the timeout period. You can do this externally (I use my own West Wind Web Monitor for this sort of thing, but there are other ways like writing a scheduled task that accesses the HTTP link) or even from within the application itself. You'll notice that the class above has a PingServer method which as part of each processing cycle also explicitly access the local site to force it to stay alive. Along the same lines you can also call something like PingServer when the application shuts down. For example, you can do the following in Application_End(): void Application_End(object sender, EventArgs e) {       if (App.Scheduler != null)     {         App.Scheduler.Dispose();         App.Scheduler = null;     }       // Force the App to be restarted immediately     new Scheduler().PingServer(); } This will in effect cause the current AppDomain to continue to shut down, but ASP.NET will immediately spin up a new one for the new request that has now hit the server and reactivates the server. So this can HELP keep any background processing  running, but it's not a 100% sure thing. Unless you have an outside pinging mechanism that hits the site there's no way for example, for the application itself restart it self when the Web server comes back up. Actually - I think this might be possible in IIS 7 with a module defined at the root Web level, but I haven't tried it. As I mentioned at the start, this is not a typical scenario and ASP.NET doesn't make exactly the best background processing platform - it's not designed for that. But I've run into this situation several times now with several different customers who are stuck with the Web Server as the implementation vehicle with no option to deploy a more traditional Windows Service and this approach works well for the quick and dirty scenario. Thinking a little further on this it seems to me that this sort of processing scenario might be a good fit for a Windows WorkFlow application - but the issue there too is how could one host the Workflow in such a way that it's always active within the context of the Web Server?  .