totoro
totoro

Reputation: 3407

how to run a background thread properly for a MVC app on shared host?

I need to run a background thread for my MVC 4 app, where the thread wakes up every hour or so to delete old files in database, then goes back to sleep. This method is below:

//delete old files from database
public void CleanDB()
{
    while (true)
    {
        using (UserZipDBContext db = new UserZipDBContext())
        {
            //delete old files
            DateTime timePoint = DateTime.Now.AddHours(-24);
            foreach (UserZip file in db.UserFiles.Where(f => f.UploadTime < timePoint))
            {
                db.UserFiles.Remove(file);
            }
            db.SaveChanges();
        }
        //sleep for 1 hour
        Thread.Sleep(new TimeSpan(1, 0, 0));
    }
}

but where should I start this thread? The answer in this question creates a new Thread and start it in Global.asax, but this post also mentions that "ASP.NET is not designed for long running tasks". My app would run on a shared host where I don't have admin privilege, so I don't think i can install a seperate program for this task.

in short,

  1. Is it okay to start the thread in Global.asax given my thread doesn't do much (sleep most of the time and small db)?

  2. I read the risk of this approach is that the thread might get killed (though not sure why). How can i detect when the thread is killed and what can i do?

  3. If this is a VERY bad idea, what else can I do on a shared host?

Thanks!

UPDATE

@usr mentioned that methods in Application_Start can be called more than once and suggested using Lazy. Before I read up on that topic, I thought of this approach. Calling SimplePrint.startSingletonThread() multiple times would only instantiate a single thread (i think). Is that correct?

public class SimplePrint
{
    private static Thread tInstance = null;

    private SimplePrint()
    {
    }

    public static void startSingletonThread()
    {
        if (tInstance == null)
        {
            tInstance = new Thread(new ThreadStart(new SimplePrint().printstuff));
            tInstance.Start();
        }
    }

    private void printstuff()
    {
        DateTime d = DateTime.Now;
        while (true)
        {
            Console.WriteLine("thread started at " + d);
            Thread.Sleep(2000);
        }
    }
} 

Upvotes: 3

Views: 9731

Answers (2)

usr
usr

Reputation: 171226

ASP.NET is not designed for long-running tasks, yes. But only because their work and data can be lost at any time when the worker process restarts.

You do not keep any state between iterations of your task. The task can safely abort at any time. This is safe to run in ASP.NET.

Starting the thread in Application_Start is a problem because that function can be called multiple times (surprisingly). I suggest you make sure to only start the deletion task once, for example by using Lazy<T> and accessing its Value property in Application_Start.

static readonly Lazy<object> workerFactory =
     new Lazy<object>(() => { StartThread(); return null; });

Application_Start:
  var dummy = workerFactory.Value;

For some reason I cannot think of a better init-once pattern right now. Nothing without locks, volatile or Interlocked which are solutions of last resort.

Upvotes: 2

giacomelli
giacomelli

Reputation: 7415

I think you should try Hangfire.

Incredibly easy way to perform fire-and-forget, delayed and recurring tasks inside ASP.NET applications. No Windows Service required.

Backed by Redis, SQL Server, SQL Azure, MSMQ, RabbitMQ.

So you don't need admin priveleges.

RecurringJob.AddOrUpdate(
() => 
{
    using (UserZipDBContext db = new UserZipDBContext())
    {
        //delete old files
        DateTime timePoint = DateTime.Now.AddHours(-24);
        foreach (UserZip file in db.UserFiles.Where(f => f.UploadTime < timePoint))
        {
        db.UserFiles.Remove(file);
        }
        db.SaveChanges();
    }    
}
Cron.Hourly);

Upvotes: 4

Related Questions