4

I'm creating a scheduler to fire events at specific times of the day, and to do this I'm spinning up Tasks (one at a time, i.e. the 'next' schedule only) with a Task.Delay of anything up to a few days delay. For example, after the last event fires on a Friday afternoon, I'll set up the next one which will be some time on Monday, so it could potentially be a TimeSpan of up to 3 days (~260,000,000 milliseconds).

Is this acceptable practice? I'm concerned that this won't be stable/robust enough for a production environment.

Here's some snippets of code to describe what I've put together:

private void SetNextEvent()
{
    TimeModel next = GetNextScheduledTime();
    Debug.WriteLine($"Next schedule [{next.TimeType}]: {next.Time.ToString("yyyy-MM-dd HH:mm:ss")}");

    TimeSpan delay = next.Time.Subtract(DateTime.Now);
    Task.Run(async () =>
    {
        await Task.Delay(delay);
        FireEvent(next);
    });
}

private void FireEvent(TimeModel time)
{
    Debug.WriteLine($"Event fired [{time.TimeType}]: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
    OnSchedulerEvent?.Invoke(this, new SchedulerEventArgs { ScheduleType = time.TimeType });
    if (_running)
        SetNextEvent();
}
Detail
  • 785
  • 9
  • 23
  • 4
    Can it work? I suppose. First thought is if the server needs to reboot you're hosed without persisting state. There are a lot of schedulers out there such as [Quartz](http://www.quartz-scheduler.net/) that are designed specifically for this. I think they would be a stronger choice to consider. – Trevor Ash Jul 07 '16 at 18:45
  • If the server reboots, the service will restart, it'll call SetNextEvent, which will return the same next scheduled time, and spin up a Task with a TimeSpan from that point... I did consider Quartz, but for what I can achieve in these two methods, it seemed a bit overkill. – Detail Jul 07 '16 at 18:47
  • 1
    You might want to check for negative `TimeSpan delay` assuming your `GetNextScheduledTime`'s behavior is to stay the same until FireEvent successfully executes. Since you may run into an issue that the clock go past the next scheduled time while the server is rebooting. Worse, it it managed to hit -1 millisecond exactly, `Task.Delay` never finishes. – ShuberFu Jul 07 '16 at 19:59
  • One possible issue is that [`Delay()` does not progress when the machine is sleeping](http://stackoverflow.com/q/38207026/41071). – svick Jul 09 '16 at 11:09

3 Answers3

3

This is totally reliable. .NET timers are very efficient. The biggest problem is that you must assume that your production apps can exit at any time. The reason that is easiest to understand is a bug that kills the process. Other reasons include reboots, app pool recycles, deployments, ....

So if you can recover your state after being killed this is fine. If you add a comment about specific concerns I'll address them.

It looks like you have a way to recover timers because you apparently can compute the next due time. Under those circumstances it's very safe to do this. You need to ensure that your code is always running e.g. right after a reboot or a crash.

Note, that IIS apps need to tolerate running multiple times concurrently. Otherwise, IIS is a great host for your scenario.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    Hi, thanks, this is exactly what I was looking for. I can recover state easily, and I have code that will detect missed schedules and replay them 'late' as long as they haven't passed a cut off. Missed schedules are actually ok, obviously not on a permanent basis, but one offs aren't an issue. My main concern was that the thread awaiting the delay would exit or become 'stale', or that the duration (days) would cause an error, or was just inadvisable for some reason. – Detail Jul 07 '16 at 20:20
  • Good question, but that is not a concern. – usr Jul 07 '16 at 20:36
0

If you're running Windows, I'd use the TaskScheduler to do what you're trying to do.

run taskschd.msc you can use that program to schedule periodic tasks.

There should be an "Create Task..." button in the panel on the right.

  • It's a different kind of "Task". taskschd.msc schedules command-line and executable-type tasks. The task parallel library is for *callable* tasks like functions, methods, subroutines, etc. – RBarryYoung Jul 07 '16 at 18:49
  • Exacty, plus using the Windows Scheduler would mean I (or rather, someone else) would have to manually configure thousands of schedules by hand from now until forever more – Detail Jul 07 '16 at 18:50
  • @RBarryYoung It's not that different. You need some code to run every day, and the Task Scheduler can do that. Even if you need your app specifically to do something, such as change it's in-memory state, you can still use the task scheduler trigger a webApi endpoint in your initial app – Sam I am says Reinstate Monica Jul 07 '16 at 18:56
  • I really don't like the idea of the Task Scheduler for this operation, it doesn't scale well, and relies on manual setup which introduces risk to an otherwise automated deployment pipeline. I suppose I could script this out with Powershell, but I'd rather not as it's a skill not everyone in the team has. – Detail Jul 07 '16 at 19:07
  • 1
    @Detail http://stackoverflow.com/questions/7394806/creating-scheduled-tasks – Sam I am says Reinstate Monica Jul 07 '16 at 19:12
  • I didn't know about this, an excellent alternative! Thanks for the heads up, I will give more consideration the Windows Scheduler now that I know about this library. – Detail Jul 07 '16 at 20:23
0

I agree that the Windows Task Scheduler is probably the best approach is you know ahead of time the schedule to run the next task.

If you do not know this ahead of time(i.e. the time to wait for the next task can vary) then I would suggest using a timer and an event handler method when the timer expires.

Each time you can set the Interval property of the timer for the time to wait. When the time expires the timer event handler will run and it can execute the ask and reset the Interval for the timer. This seems a little cleaner then a Task.Delay.

  • Is there anything to justify it being a better solution? I did consider and begin to implement a solution using Timers, but to me the async await approach seemed cleaner, and is a pattern I'm keen to promote in the team. Underneath the covers I kind of wondered if they're both doing relatively the same thing anyway... – Detail Jul 07 '16 at 19:02
  • The Task.Delay does kick off a timer in the background. The processing is the same as far as resource consumption and unblocking threads. I'm not sure if I would use the word "better" to describe either approach. – Rodney Ringler Jul 08 '16 at 20:12