6

I have an ASP.NET MVC4 application. Suppose that I wanted to execute some code (such as emailing a user) 2 days after a user has signed up.

One way is to have code executed on every request which scans the database for users and their sign-up time and execute code based on that. This seems messy and it depends on people visiting the application frequently.

Another way is to write a separate application that runs on the server 24/7 and performs the checking itself, perhaps scanning the database at 12:00am every day and performing its own code.

What's the good way to go about this?

1
  • You can also have functions called from your application start function that lives in the global.asax file. This works out well if you have control over when the app pool restarts.
    – Lazy Coder
    Apr 15, 2013 at 3:31

6 Answers 6

6

A scheduled task triggered by either the Task Scheduler or Sql Server is the way to go here. But if you really want to manage it within your webapp, you might want to look at something like Quartz.NET. It will let you do scheduled tasks from the CLR. Then your challenge is "how do I make sure the AppDomain stays up to run the tasks."

Another way to do it as a scheduled task yet keep most of the "smarts" on the server is to make it a task that can be called over HTTP with some sort of authorization key. This lets you write a relatively simple program to call it -- if not a simple shell script -- and to keep most of the complexity in the web app which is likely already capable of running the task.

Either way rolling your own task manager is really a path fraught with peril.

1
  • +1 for easy options, and also for more advanced Quartz.net option
    – ozz
    Apr 15, 2013 at 15:04
4

There are free, easy, professional, and robust solutions like hangfire designed to do exactly this, with retries and logging and stuff. I recommend it.

(If that's impossible for some reason: In the old days we just created a .NET "console" application and used Scheduled Tasks (which is built into Windows) to run it on a schedule (e.g.: once every hour). Another popular option was to create your own .NET Windows Service. A bit more "serious" and possibly more robust. Trickier to set up and a bit harder to debug, though).

3

A third way is to set up a "task server," which could be its own server, or an independent thread on your server.

Create a class that contains two members: a timestamp, and a "task," which is not a .NET Task, but a representation of all of the information the server will need to do whatever it is that needs to be done.

Create a threadsafe priority queue of objects of this class, that will sort based on timestamp. The first object to come off the queue should always be the one with the lowest timestamp.

The server thread runs like this:

while true
   var time = queue.peek.timestamp
   if time > Now
      sleep(Now - time)
   queue.pop.task.execute
   if queue.empty
      block until there's an item in the queue
end

That's the basic concept. Scheduling repetitive tasks (every day at midnight do X) and ensuring that scheduled tasks are preserved even in the event of a server failure are left as an exercise for the reader.

2
  • That's really cool. So could I create a new class, in which one instance is created at application start-up, which runs in it's own thread to do as you described? If the class only ever reads data then is it thread safe? Apr 15, 2013 at 1:04
  • @RowanFreeman: It is, as long as any shared data it's reading from is accessed in a safe manner. The obvious example here would be the priority queue; the Pop operation on it involves rearranging the queue's internals, and should only be done while the queue is locked to prevent concurrent access. Apr 15, 2013 at 4:10
2

The approach we use is to create an NServiceBus Saga with a timeout.

The Web application sends a message, the saga is started and requests a timeout for 2 days time. If no other action has occurred which would prevent the email being sent, the timeout wakes the saga back up and sends the email. The cancellation request also sends a message which wakes the saga and sets a cancelled flag.

This approach is nicely decoupled from the web application and easily scaled, also the messages are queued so we can shut down the processing side for updates without shutting off the web app.

The other nice thing about this architecture is that it is event based rather than polling based so there is less contention and traffic on the database.

1

Definitely do NOT use your first idea! lol That would lead to massive problems in the backend when things start to get bigger.

If your backend is MS Sql Server (guessing by the tags in your post), sql server has the ability to schedule tasks, elimination the need to "ping" your app to cause the task to execute (which is very unreliable if important things depend on it). If this is an option for you, then it is probably the best route, unless your apps business logic needs access to the task as well for some reason. If that is the case, the options mentions in other answers will serve well. Another option to note is if you go with scheduled jobs in sql server and have several jobs you would like to be done sequentially or asynchronously, you could couple this with triggers in appropriate locations that start the next (or parallel) task(s).

One note on the task server idea though: The idea of a task server would be fine but the idea of creating a dedicated thread on the server could be very unreliable due to app pool restarts and, more importantly, thread timeouts (for say, tasks that run once a day or every few days). Just a suggestion.

0
1

As of ASP.Net 4.0 you have access to an Auto-Start feature for your ASP.Net applications, which means that you can have a thread running like a Windows Service, but keep it running in the context of your ASP.Net application. It's a little fiddly to configure the first time you use it, but once you have it set up, it's very handy for keeping your whole application in one place.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.