Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a Timer Job the right tool for this job?

Suppose you have an application that lets users monitor all of their milk jugs. All of the milk jugs will expire at some point.

I want to monitor all of the milk jugs to see if their "isExpired" property should be true or false.

Should I iterate over all jugs every once in a while with something like a timerjob to update the properties? What if I want instant feedback once a jug has expired? What tools are available to me in the .NET/C# world to make this inexpensive / efficient?

I'll eventually use something like signalR to push notifications to web clients once a jug expires - I just don't know how to determine WHEN a jug has expired without iterating over everything constantly.

==

Forgive me for cramming two questions into this, but obviously it's only important to send notifications when a user is watching the jugs. So perhaps a timerjob that runs only while a user's session is alive would work? Any tips/recommended reading here would be awesome.

like image 715
RobVious Avatar asked Mar 28 '13 22:03

RobVious


2 Answers

If you want instant notification when a jug expires, you can do the following:

Scan the list of jugs and find the one with the earliest expiration date
Set a timer to expire at that date/time.
When the timer expires, remove that jug from the list.
Scan the list of jugs to find any other jugs that expire at this time.
Send notifications for all the expired jugs.
Go to step 1.

There are a few problems with this algorithm:

  • If you add a jug, you have to check to see if its expiration time is before the current earliest expiration time. If so, you have to reset the timer for the current jug's expiration time.
  • Similarly, if you remove a jug from the list, you have to check to see if it's the one you're waiting on for expiration. If so, you have to find the new earliest jug and reset the timer.
  • Scanning a very large list of jugs can get expensive, although you only have to do it when a jug expires.

You can relieve yourself of having to scan the list by sorting it. Then you know that the earliest expiration date is always on top of the list. When you add a new jug, you just insert by expiration time. You can use binary search to determine where to insert.

Or, you could use a priority queue (binary heap or perhaps a skip list) to store the jugs by expiration date. Which data structure you use depends on how many items you have and how frequently you'll be updating it. The more jugs and the more frequently you access them, the more concerned you should be with an efficient data structure.

The beauty of this approach is that your timer only ticks when you need to expire a jug. You never poll.

It's simple to create a one-shot timer like this in .NET:

TimeSpan expirationDelay = jug.ExpirationDateTime - DateTime.Now;

System.Threading.Timer jugTimer = new System.Threading.Timer(
    JugExpirationCallback, null, expirationDelay, TimeSpan.FromMilliseconds(-1));

The last parameter to the constructor tells it not to be a periodic timer, but rather to fire once and quit. You can then update the timer for the next jug by calling the timer's Change method.

Note that calculation of the expirationDelay doesn't take Daylight Saving Time into account. If you want to do that, you'll want to convert the DateTime values to UTC before doing the calculation. Or, you can use a WaitableTimer to do it. There's a link for the WaitableTimer source at the end of the article.

Additional info after comment

I suspect, after your comment, that you want to notify logged-in users of expiring jugs. I would probably go with a collection (a list or priority queue) that contains one jug for each logged-in user: the earliest one to expire. As a user's item expires, you can do a database query to get the one that will expire next for that user, and add it to the list. This depends, of course, on how many users you expect to have, how many jugs each one has, and how often things will expire.

You could try to keep them all in memory, but then you have to manage updates. That is, if the database gets updated, you have to update the in-memory representation, too. If you only keep one per user, that problem is greatly simplified. And it also takes a lot less memory.

Regardless of how you decide to populate the list, the basic idea stays the same: create one timer that's configured to fire when the earliest of the items is to expire. As each item expires, reset the timer for the next one.

How you decide to add items to the list is "just a detail." I can't make more specific recommendations because I don't know enough about your application.

like image 152
Jim Mischel Avatar answered Sep 19 '22 16:09

Jim Mischel


The simplest solution is to use a Timer (http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx) and then throw an event whenever the timer expires. The timer can be set/reset based on whether the user is watching it or not. Once the event is thrown on the server, you can use SignalR to call the concerned method on the client/notify the user.

like image 38
Abhishek Nanda Avatar answered Sep 19 '22 16:09

Abhishek Nanda