开发者

Background task in a ASP webapp

I'm fairly new to C#, and recently built a small webapp using .NET 4.0. This app has 2 parts: one is designed to run permanently and will continuously fetch data from given resources on the web. The other one accesses that data upon request to analyze it. I'm struggling with the first part.

My initial approach was to set up a Timer object that would execute a fetch operation (whatever that operation is doesn't really matter here) every, say, 5 minutes. I would define that timer on Application_Start and let it live after that.

However, I recently realized that applications are created / destroyed based on user requests (from my observation they seem to be destroyed after some time of inactivity). As a consequence, my background activity will stop / resume out of my control whe开发者_JAVA百科re I would like it to run continuously, with absolutely no interruption.

So here comes my question: is that achievable in a webapp? Or do I absolutely need a separate Windows service for that kind of things?

Thanks in advance for your precious help!

Guillaume


While doing this on a web app is not ideal..it is achievable, given that the site is always up.

Here's a sample: I'm creating a Cache item in the global.asax with an expiration. When it expires, an event is fired. You can fetch your data or whatever in the OnRemove() event.

Then you can set a call to a page(preferably a very small one) that will trigger code in the Application_BeginRequest that will add back the Cache item with an expiration.

global.asax:

private const string VendorNotificationCacheKey = "VendorNotification";
private const int IntervalInMinutes = 60; //Expires after X minutes & runs tasks

protected void Application_Start(object sender, EventArgs e)
{

   //Set value in cache with expiration time
   CacheItemRemovedCallback callback = OnRemove;

   Context.Cache.Add(VendorNotificationCacheKey, DateTime.Now, null, DateTime.Now.AddMinutes(IntervalInMinutes), TimeSpan.Zero,
                    CacheItemPriority.Normal, callback);
}

private void OnRemove(string key, object value, CacheItemRemovedReason reason)
{
    SendVendorNotification();

    //Need Access to HTTPContext so cache can be re-added, so let's call a page. Application_BeginRequest will re-add the cache.
    var siteUrl = ConfigurationManager.AppSettings.Get("SiteUrl");
    var client = new WebClient();
    client.DownloadData(siteUrl + "default.aspx");
    client.Dispose();

}

private void SendVendorNotification()
{
    //Do Tasks here
}

protected void Application_BeginRequest(object sender, EventArgs e)
{
    //Re-add if it doesn't exist
    if (HttpContext.Current.Request.Url.ToString().ToLower().Contains("default.aspx") &&
        HttpContext.Current.Cache[VendorNotificationCacheKey] == null)
    {
        //ReAdd
        CacheItemRemovedCallback callback = OnRemove;
        Context.Cache.Add(VendorNotificationCacheKey, DateTime.Now, null, DateTime.Now.AddMinutes(IntervalInMinutes), TimeSpan.Zero,
                          CacheItemPriority.Normal, callback);
    }
}

This works well, if your scheduled task is quick. If it's a long running process..you definitely need to keep it out of your web app.

As long as the 1st request has started the application...this will keep firing every 60 minutes even if it has no visitors on the site.


I suggest putting it in a windows service. You avoid all the hoops mentioned above, the big one being IIS restarts. A windows service also has the following benefits:

  1. Can automatically start when the server starts. If you are running in IIS and your server reboots, you have to wait until a request is made to start your process.
  2. Can place this data fetching process on another machine if needed
  3. If you end up load-balancing your website on multiple servers, you could accidentally have multiple data fetching processes causing you problems
  4. Easier to main the code separately (single responsibility principle). Easier to maintain the code if it's just doing what it needs to do and not also trying to fool IIS.


Create a static class with a constructor, creating a timer event. However like Steve Sloka mentioned, IIS has a timeout that you will have to manipulate to keep the site going.

using System.Runtime.Remoting.Messaging;

public static class Variables
{
static Variables()
{
    m_wClass = new WorkerClass();

    // creates and registers an event timer
    m_flushTimer = new System.Timers.Timer(1000);
    m_flushTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnFlushTimer);
    m_flushTimer.Start();
}

private static void OnFlushTimer(object o, System.Timers.ElapsedEventArgs args)
{
    // determine the frequency of your update
    if (System.DateTime.Now - m_timer1LastUpdateTime > new System.TimeSpan(0,1,0))
    {
        // call your class to do the update
        m_wClass.DoMyThing();
        m_timer1LastUpdateTime = System.DateTime.Now;
    }
}

private static readonly System.Timers.Timer m_flushTimer;
private static System.DateTime m_timer1LastUpdateTime = System.DateTime.MinValue;
private static readonly WorkerClass m_wClass;
}

public class WorkerClass
{
public delegate WorkerClass MyDelegate();

public void DoMyThing()
{
    m_test = "Hi";
    m_test2 = "Bye";
    //create async call to do the work
    MyDelegate myDel = new MyDelegate(Execute);
    AsyncCallback cb = new AsyncCallback(CommandCallBack);
    IAsyncResult ar = myDel.BeginInvoke(cb, null);
}

private WorkerClass Execute()
{
    //do my stuff in an async call
    m_test2 = "Later";
    return this;
}

public void CommandCallBack(IAsyncResult ar)
{
    // this is called when your task is complete
    AsyncResult asyncResult = (AsyncResult)ar;
    MyDelegate myDel = (MyDelegate)asyncResult.AsyncDelegate;
    WorkerClass command = myDel.EndInvoke(ar);

    // command is a reference to the original class that envoked the async call
    // m_test will equal "Hi"
    // m_test2 will equal "Later";
}

private string m_test;
private string m_test2;
}


I think you can can achieve it by using a BackgroundWorker, but i would rather suggest you to go for a service.


Your application context lives as long as your Worker Process in IIS is functioning. In IIS there's some default timeouts for when the worker process will recycle (e.g. Number of Idle mins (20), or regular intervals (1740).

That said, if you adjust those settings in IIS, you should be able to have the requests live, however, the other answers of using a Service would work as well, just a matter of how you want to implement.


I recently made a file upload functionality for uploading Access files to the database (not the best way but just a temporary fix to a longterm issue). I solved it by creating a background thread that ran through the ProcessAccess function, and was deleted when completed.

Unless IIS has a setting in which it kills a thread after a set amount of time regardless of inactivity, you should be able to create a thread that calls a function that never ends. Don't use recursion because the amount of open functions will eventually blow up in you face, but just have a for(;;) loop 5,000,000 times so it'll keep busy :)


Application Initialization Module for IIS 7.5 does precisely this type of init work. More details on the module are available here Application Initialization Module

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜