7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GenServerのhandle_info/2とProcess.send_after/4の組み合わせによる定期実行

Posted at

自分は、Elixirで一定間隔で繰り返し実行される処理を:timer.sleep/1と再帰処理の組み合わせで実施していた。

しかし、GerServerを利用する分には、Process.send_after/4GenServer.handle_info/2を利用すれば、シンプルに実装できることを先日知った。

具体的な対応方法としては、GenServer.init/1中でProcess.send_after/4を実行。指定の時間後にGenServer.handle_info/2が呼び出されるので、その中で再びProcess.send_after/4を呼び出せばよい。

元ネタはこちら

また、Process.send_after/4の詳細はこちらを参照。

実装

一定間隔でFizzBuzzの結果を返すGenServerのコードを書いてみる。

まず、プロジェクトの作成。
「Supervisor」ありでプロジェクト作成をしているが、別に無くても良い。

$mix new fizzbuzz --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/fizzbuzz.ex
* creating lib/fizzbuzz/application.ex
* creating test
* creating test/test_helper.exs
* creating test/fizzbuzz_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd fizzbuzz
    mix test

Run "mix help" for more commands.

つぎに、定期実行されるGernServerのコードを用意する。
内容としては、一定間隔でFizzBuzzの値がコンソールに出力されるようにした。
GenServerを利用しているので、FizzBuzzの結果は"状態"の値を利用して算出する実装とした。そのため、FizzBuzz実行後の"状態"はインクリメントしていく。

./lib/fizzbuzz/fizzbuzz_auto.ex
defmodule Fizzbuzz.Auto do
  use GenServer

  def calc_fb({n, 0, 0}), do: {n, "FizzBuzz"}
  def calc_fb({n, 0, _}), do: {n, "Fizz"}
  def calc_fb({n, _, 0}), do: {n, "Buzz"}
  def calc_fb({n, _, _}), do: {n, to_string(n)}

  def fizzbuzz(n) do
    cond do
      n < 1 -> {n , "Cannot calculate"}
      true  -> calc_fb({n, rem(n,3), rem(n, 5)})
    end
  end

  def schedule_work do
    # In 5 Seconds
    Process.send_after(self(), :work, 5 * 1000)
  end

  # 以降、GenServerの実装

  def init(state) do
    IO.puts("Fizzbuzz.Auto: init/1  call")
    schedule_work()
    {:ok, state}
  end

  def handle_info(:work, current_num) do
    {n, v} = current_num |> fizzbuzz()
    IO.puts("Fizzbuzz.Auto: schedule_work : #{n} : #{v}")

    # Reschedule once more
    schedule_work()

    {:noreply, current_num + 1}
  end
end

init/1から、Process.send_after/4を実行する関数schedule_work/0を呼び出す。この時、Process.send_after/4では、引数に「自分自身のPID」「メッセージ(今回は:work)」「待ち時間(今回は5000ms)」を渡してる。

5秒(5000ms)後に、handle_info(:work, current_num)が呼び出され、この中でFizzBuzz処理の実行後にふたたび関数schedule_work/0が呼び出される。

実行結果

実際に動かしてみる。
iex -S mixでiexを起動後、GenServer.start(Fizzbuzz.Auto, 1)でFizzbuzz.Autoを実行する。ここで、「1」は初期の"状態"である。

$iex -S mix
Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Compiling 3 files (.ex)
Generated fizzbuzz app
Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> GenServer.start(Fizzbuzz.Auto, 1)
Fizzbuzz.Auto: init/1  call
{:ok, #PID<0.168.0>}
Fizzbuzz.Auto: schedule_work : 1 : 1
Fizzbuzz.Auto: schedule_work : 2 : 2
Fizzbuzz.Auto: schedule_work : 3 : Fizz
Fizzbuzz.Auto: schedule_work : 4 : 4
Fizzbuzz.Auto: schedule_work : 5 : Buzz
Fizzbuzz.Auto: schedule_work : 6 : Fizz
Fizzbuzz.Auto: schedule_work : 7 : 7
Fizzbuzz.Auto: schedule_work : 8 : 8
Fizzbuzz.Auto: schedule_work : 9 : Fizz
Fizzbuzz.Auto: schedule_work : 10 : Buzz
Fizzbuzz.Auto: schedule_work : 11 : 11
Fizzbuzz.Auto: schedule_work : 12 : Fizz
Fizzbuzz.Auto: schedule_work : 13 : 13
Fizzbuzz.Auto: schedule_work : 14 : 14
Fizzbuzz.Auto: schedule_work : 15 : FizzBuzz
Fizzbuzz.Auto: schedule_work : 16 : 16
〜(以下、続いていく)〜

実行すると、5秒ごとにメッセージが出力される。

課題

Process.send_after/4の読み出しをinit/1の時点から開始しないと、うまく動いてくれない。
例えば、init/1schedule_work/0を呼び出ないでGenServerとして起動し、その後にschedule_work/0を実行しても、iex上では何も起こっていないように見える。

$cat ./lib/fizzbuzz/fizzbuzz_auto.ex

〜(略)〜
  def init(state) do
    IO.puts("Fizzbuzz.Auto: init/1  call")
#    schedule_work()
    {:ok, state}
  end
〜(略)〜

$iex -S mix

iex(1)> GenServer.start(Fizzbuzz.Auto, 1)
Fizzbuzz.Auto: init/1  call
{:ok, #PID<0.146.0>}
iex(2)> Fizzbuzz.Auto.schedule_work()
#Reference<0.2803571744.3830972420.104800>
iex(3)>

この辺りの理屈はまだ理解しきれていないので、わかったら追記していきたい。

参考情報

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?