How to create an event emitter with elixir, the otp way
Asked Answered
A

2

11

What is the best way in elixir to create a foreground process that tick on every given amount of time?

My main problem is that an approach like:

defmoulde Ticker do
  def tick do
    do_something()
    :timer.sleep(1000)
    tick
  end
end

works, but is wrong by design. It's definitely not ticking every second, but every second PLUS the time for do_something() to complete. I could spawn a process to handle the "something" but still there is a small lag.

Also, I'm trying to build a mix app, there is some GenServers involved, and a main foreground process (the one I'm asking here) that should call the servers every x seconds. Is there an otp way of doing this?

Attalanta answered 14/7, 2015 at 14:57 Comment(0)
I
18

I think timer:apply_interval/4 should suit your needs. It is used something like this:

defmodule Tick do
  def tick do
    IO.inspect(:tick)
  end
end

:timer.apply_interval(:timer.seconds(1), Tick, :tick, [])

The arguments in order are the interval, a module name, the function to call and the arguments to call it with. Then you can send messages to your GenServers inside that function. Check out the full documentation here: apply_interval/4

Another thing to consider is that if your tick function is only ever sending simple messages to some GenServers then it is probably extremely fast, so your implementation might be OK, once wrapped in a Task and plugged into the supervision tree (if you don't care about the potential slight drift that is).

Iinde answered 14/7, 2015 at 15:16 Comment(2)
I care about the potential time drift. How would I avoid any time drift?Stepfather
apply_interval should be pretty decent, but the docs only state that "The time-outs are not exact, but are at least as long as requested".Dismal
H
4

If you prefer sending a message instead of calling a function at a given interval, you can use :timer.send_interval/3

E.g. if you want to send a message to a GenServer every second as OP mentions in their answer, you could do the following:

:timer.send_interval(:timer.seconds(1), target_pid, :tick)

Where target_pid is the PID of the GenServer you want to send a message to. Then you can handle this message in your GenServer like this:

def handle_info(:tick, state) do
  do_something()
  {:noreply, state}
end
Hussein answered 5/8, 2020 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.