Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 where 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

like image 380
guidupuy Avatar asked Jun 20 '11 23:06

guidupuy


3 Answers

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.

like image 144
Ed B Avatar answered Nov 04 '22 00:11

Ed B


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.
like image 44
Jason Avatar answered Nov 03 '22 22:11

Jason


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;
}
like image 1
Crater Avatar answered Nov 03 '22 23:11

Crater