(Originally published February 1, 2013)
I see questions like this fairly regularly:
I need to write a program that checks a database every five minutes, and processes any new records. What I have right now is this:
void Main() { while (true) { CheckDatabaseForUpdates(); Thread.Sleep(300000); // 5 minutes, in milliseconds }
}
Is this the best way, or should I be using threads?
I’ve said before that Thread.Sleep
is a red flag indicating that something is not quite right. And when a call to Sleep
is in a loop, it’s a near certainty that there is a real problem. Most Sleep
loops can be re-coded to use a timer, which then frees the main thread to do other things.
In this example, the “other thing” the main thread could be doing is waiting for a prompt from the user to cancel the program. As it stands, the only way to close the program would be to abnormally terminate the process (Control+C, for example, or through Task Manager). That could be a very bad thing, because the program might be in the middle of updating the database when you terminate it. That’s a virtually guaranteed path to data corruption.
I’m not going to show how to refactor this little program to use a timer because doing so is fixing a solution that is fundamentally broken at a higher level.
How can this program be broken?
I’m so glad you asked.
The program is broken because it’s doing a poor job of implementing a feature that already exists in the operating system. The program is occupying memory doing nothing most of the time. It naps, wakes up periodically, looks around, maybe does some work, and then goes back to sleep. It’s like a cat that gets up to stretch every five minutes and maybe bats a string around for a bit before going back to sleep.
Operating systems already have a way to implement catnap programs. In the Linux world, the cron daemon does it. Windows has Task Scheduler and the schtasks command line utility. These tools let you schedule tasks to execute periodically. Using them has several advantages over rolling your own delay loop.
- You can change or terminate the schedule without having to recompile the program. Yes, you could make the program read the delay time from a configuration file, but that just makes the fundamentally broken program a little more complicated.
- Task schedules are remembered when the computer is restarted, freeing you from having to start it manually or figure out how to put it in the startup menu.
- If a catnap program crashes, it must be restarted manually. A scheduled task, on the other hand, will log the error, and run the next time.
- The program doesn’t consume system resources when it’s not doing useful work. It’s like a cat that’s not even there when it’s sleeping. (Which would make for a cat that you don’t see very often, and only for very brief periods.)
Computer programs aren’t cats. If a program isn’t doing active work or maintaining some in-memory state that’s required in order to respond to requests, then it shouldn’t be running. If all your program does is poll a resource on a regular schedule, then you’ve written a catnap that is better implemented with the OS-provided task scheduler.