views:

3811

answers:

5

I'm working on a little web crawler that will run in the system tray and crawl a web site every hour on the hour.

What is the best way to get .NET to raise an event every hour or some other interval to perform some task. For example I want to run an event every 20 minutes based on the time. The event would be raised at:

00:20
00:40
01:00
01:20
01:40

and so on. The best way I can think of to do this is by creating a loop on a thread, that constantly checks if the time is divisible by a given interval and raises a callback event if the time is reached. I feel like there has got to be a better way.

I'd use a Timer but I'd prefer something that follows a "schedule" that runs on the hour or something along those lines.

Without setting up my application in the windows task scheduler is this possible?

UPDATE:
I'm adding my algorithm for calculating the time interval for a timer. This method takes a "minute" parameter, which is what time the timer should trigger a tick. For example, if the "minute" parameter is 20, then the timer will tick at the intervals in the timetable above.

int CalculateTimerInterval(int minute)
{
    if (minute <= 0)
        minute = 60;
    DateTime now = DateTime.Now;

    DateTime future = now.AddMinutes((minute - (now.Minute % minute))).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);

    TimeSpan interval = future - now;

    return (int)interval.TotalMilliseconds;
}

This code is used as follows:

static System.Windows.Forms.Timer t;
const int CHECK_INTERVAL = 20;


static void Main()
{
    t = new System.Windows.Forms.Timer();
    t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
    t.Tick += new EventHandler(t_Tick);
    t.Start();
}

static void t_Tick(object sender, EventArgs e)
{
    t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
}
+1  A: 

System.Windows.Forms.Timer (or System.Timers.Timer)

but since now you say you don't want to use Timers, you can run a lightweight wait process on another thread (check time, sleep a few seconds, check time again...) or make a component that raises an event (using a lightweight wait process) on certain scheduled times or intervals

Steven A. Lowe
+6  A: 

System.Timers.Timer. If you want to run at specific times of the day, you will need to figure out how long it is until the next time and set that as your interval.

This is just the basic idea. Depending on how precise you need to be you can do more.

int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60;
tvanfosson
Agreed - timers are the way to go. "Loops that check" are just another way of saying "polling," and polling kills. Using a timer is your best bet.
Keithius
+5  A: 

You may find help from Quartz.net http://quartznet.sourceforge.net/

suhair
Quartz.net looks like an awesome library to use, but for the small app I'm making I think it's a little overkill (my app is less than 500 lines of code).
Dan Herbert
Even with a small app, Quartz is very flexible and adds very little overhead in terms of lines of code. It's about six lines of code for the initialization and scheduling of a timed job.
Brian Vallelunga
+1  A: 

Here is an example of a lightweight system using thread timing and an asynch call.

I know there are some downsides, but I like using this instead of a timer when kicking off a long running process (like schedualed backend services). Since it runs inline in the timer thread, you don't have to worry about it getting kicked off again before the the original call has finished. This could be extended quite a bit to make it use an array of datetimes as the trigger times or add some more abilities to it. I am sure some of you guys out there know some better ways.

    public Form1()
    {
        InitializeComponent();

        //some fake data, obviously you would have your own.
        DateTime someStart = DateTime.Now.AddMinutes(1);
        TimeSpan someInterval = TimeSpan.FromMinutes(2);

        //sample call
        StartTimer(someStart,someInterval,doSomething);
    }

    //just a fake function to call
    private bool doSomething()
    {
        DialogResult keepGoing = MessageBox.Show("Hey, I did something! Keep Going?","Something!",MessageBoxButtons.YesNo);
        return (keepGoing == DialogResult.Yes);
    }

    //The following is the actual guts.. and can be transplanted to an actual class.
    private delegate void voidFunc<P1,P2,P3>(P1 p1,P2 p2,P3 p3);
    public void StartTimer(DateTime startTime, TimeSpan interval, Func<bool> action)
    {
        voidFunc<DateTime,TimeSpan,Func<bool>> Timer = TimedThread;
        Timer.BeginInvoke(startTime,interval,action,null,null);
    }

    private void TimedThread(DateTime startTime, TimeSpan interval, Func<bool> action)
    {
        bool keepRunning = true;
        DateTime NextExecute = startTime;
        while(keepRunning)
        {
            if (DateTime.Now > NextExecute)
            {
                keepRunning = action.Invoke();
                NextExecute = NextExecute.Add(interval);
            }
            //could parameterize resolution.
            Thread.Sleep(1000);
        }            
    }
Xenophile
A: 

Another strategy for this would be to record the LAST TIME that the process was run and determine if your desired interval has elapsed since that time. In this strategy, you would code your event to fire if the elapsed time is equal to OR GREATER THAN the desired interval. In this way you can handle instances where long intervals (once per day, for example) could be missed if the computer were to be down for some reason.

So for example:

  • lastRunDateTime = 5/2/2009 at 8pm
  • I want to run my process every 24 hours
  • On a timer event, check whether 24 hours OR MORE passed since the last time the process was run.
  • If yes, run the process, update lastRunDateTime by adding the desired interval to it (24 hours in this case, but whatever you need it to be)

Obviously, for this to recover after the system has gone down, you will need to store lastRunDateTime in a file or database somewhere so the program could pick up where it left off on recovery.

Casey Gay