Tickers in Crystal

Tickers are for when you want to do something repeatedly at regular intervals. Here’s an example of a ticker that ticks periodically until we stop it.

require "time"

ticker = Time::Span.new(0, 0, 0, 500)
done = Channel(Bool).new

spawn do
  loop do
    select
    when done.receive
      break
    when timeout(ticker)
      puts "Tick at #{Time.local}"
    end
  end
end

# Tickers can be stopped. Once a ticker is stopped it won't receive any more values.
# We'll stop ours after 1600ms.
sleep 1.6.seconds
done.send(true)
puts "Ticker stopped"

In this Crystal program, we’re using a combination of spawn, loop, select, and timeout to simulate a ticker. The ticker is defined as a Time::Span of 500 milliseconds.

We create a Channel named done to signal when we want to stop the ticker.

The spawn block creates a new fiber (Crystal’s lightweight thread) that runs concurrently with the main program. Inside this fiber, we have an infinite loop that uses select to either receive from the done channel (which breaks the loop) or timeout after the ticker duration (which simulates a tick).

After creating the ticker, we sleep the main thread for 1.6 seconds, allowing the ticker to tick a few times. Then we send a signal through the done channel to stop the ticker.

When we run this program, the ticker should tick 3 times before we stop it:

$ crystal run tickers.cr
Tick at 2023-06-07 15:30:00.500
Tick at 2023-06-07 15:30:01.000
Tick at 2023-06-07 15:30:01.500
Ticker stopped

Crystal’s concurrency model is based on fibers and channels, which is similar to Go’s goroutines and channels. However, Crystal uses spawn to create fibers instead of the go keyword, and it doesn’t have a built-in ticker type. We’ve simulated the ticker behavior using select and timeout.