Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

自分は、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)>

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

参考情報

MzRyuKa
のんびりごろごろ、ネコ、うさまる、技術の話は大好きです。 仕事では主にRubyを利用、バッチ周りをgolangやPythonに置き換え。 最近はElixirがお気に入り。Elixirの技術同人誌「PhoenixLiveViewとNervesをさわるElixirへのいざない、Elixirへのいざない2〜もっとNervesをさわる〜、錬金術師見習いエリィと歩むたのしいElixir」を技術書典で頒布
https://mzryuka.hatenablog.jp/
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away