Running a Code Block Periodically

Problem

You want to run some Ruby code (such as a call to a shell command) repeatedly at a certain interval.

Solution

Create a method that runs a code block, then sleeps until it's time to run the block again:

	def every_n_seconds(n)
	 loop do
	 before = Time.now
	 yield
	 interval = n-(Time.now-before)
	 sleep(interval) if interval > 0
	 end
	end
	every_n_seconds(5) do
	 puts "At the beep, the time will be #{Time.now.strftime("%X")}…beep!"
	end
	# At the beep, the time will be 12:21:28… beep!
	# At the beep, the time will be 12:21:33… beep!
	# At the beep, the time will be 12:21:38… beep!
	# …

 

Discussion

There are two main times when you'd want to run some code periodically. The first is when you actually want something to happen at a particular interval: say you're appending your status to a log file every 10 seconds. The other is when you would prefer for something to happen continuously, but putting it in a tight loop would be bad for system performance. In this case, you compromise by putting some slack time in the loop so that your code isn't always running.

The implementation of every_n_seconds deducts from the sleep time the time spent running the code block. This ensures that calls to the code block are spaced evenly apart, as close to the desired interval as possible. If you tell every_n_seconds to call a code block every five seconds, but the code block takes four seconds to run, every_n_seconds only sleeps for one second. If the code block takes six seconds to run, every_n_seconds won't sleep at all: it'll come back from a call to the code block, and immediately yield to the block again.

If you always want to sleep for a certain interval, no matter how long the code block takes to run, you can simplify the code:

	def every_n_seconds(n)
	 loop do
	 yield
	 sleep(n)
	 end
	end

In most cases, you don't want every_n_seconds to take over the main loop of your program. Here's a version of every_n_seconds that spawns a separate thread to run your task. If your code block stops the loop by with the break keyword, the thread stops running:

	def every_n_seconds(n)
	 thread = Thread.new do
	 while true
	 before = Time.now
	 yield
	 interval = n-(Time.now-before)
	 sleep(interval) if interval > 0
	 end
	 end
	 return thread
	end

In this snippet, I use every_n_seconds to spy on a file, waiting for people to modify it:

	def monitor_changes(file, resolution=1)
	 last_change = Time.now
	 every_n_seconds(resolution) do
	 check = File.stat(file).ctime
	 if check > last_change
	 yield file
	 last_change = check
	 elsif Time.now - last_change > 60
	 puts "Nothing's happened for a minute, I'm bored."
	 break
	 end
	 end
	end

That example might give output like this, if someone on the system is working on the file /tmp/foo:

	thread = monitor_changes("/tmp/foo") { |file| puts "Someone changed #{file}!" }
	# "Someone changed /tmp/foo!"
	# "Someone changed /tmp/foo!"
	# "Nothing's happened for a minute; I'm bored."
	thread.status # => false

 

See Also

  • Recipe 3.13, "Waiting a Certain Amount of Time"
  • Recipe 23.4, " Running Periodic Tasks Without cron or at"






Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399
Simiral book on Amazon

Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net