GenServer で定期的に何かする動作を(今日こそ)実装してみます。
はじめてなElixir(19) GenServer で定期的なお仕事をする(まだ終わってない編) で、中途半端な状態で終わってしまい、放置していました。今日はこれを終わらせます。
改めて、GenServer で定期的に何かする動作を GenServerで実装してる風に見えないようにして 実装してみます。
Interface をきちんと使う
改めて Elixirの 16.5節を御覧ください。リファレンスの Client/Server APIs も。
@behavior と @impl を使って、さらに GenServer をラップする
前回は @impl
と @behavior
に感心して、肝心の GenServer を見せなくするラッパ関数を書くのを完全に忘れてました。
defmodule Temp16 do
@behaviour GenServer
def start_link(pname) do
GenServer.start_link(__MODULE__, nil, name: pname)
end
def next(pname) do
GenServer.cast(pname, :next)
end
def last(pname) do
GenServer.call(pname, :last)
end
@impl GenServer
def init(_void) do
{:ok, []}
end
@impl GenServer
def handle_cast(:next, []) do
{:noreply, [0]}
end
@impl GenServer
def handle_cast(:next, [head | tail]) do
{:noreply, [head + 1 | [head | tail]]}
end
@impl GenServer
def handle_call(:last, _from, []) do
{:reply, {:error, []}, []}
end
@impl GenServer
def handle_call(:last, _from, [head | tail]) do
{:reply, {:ok, head}, [head | tail]}
end
def test() do
Temp16.start_link(:mypid)
IO.inspect(Temp16.last(:mypid)) # should get :error
IO.inspect(Temp16.next(:mypid))
IO.inspect(Temp16.last(:mypid))
IO.inspect(Temp16.next(:mypid))
IO.inspect(Temp16.last(:mypid))
IO.inspect(Temp16.next(:mypid))
IO.inspect(Temp16.last(:mypid))
IO.inspect(Temp16.next(:mypid))
IO.inspect(Temp16.last(:mypid))
:ok
end
test() 関数を見て下さい。GenServer が見えなくなってます。Temp16 って名前に色気がないのでありがたみがないですけど、とにかく GenServer は見えなくなりました。
iex(1)> Temp16.test()
{:error, []}
:ok
{:ok, 0}
:ok
{:ok, 1}
:ok
{:ok, 2}
:ok
{:ok, 3}
:ok
とまあ、たったこれだけのことを忘れて、追加するのに大変間が空いてしまいました。
定期的に自律的な動作を行わせるのに GenServer のラップもする
調子よくラッパ関数を書けたので、定期的にプロセスが刺激を受けてチョコチョコ動作するモジュールにもラッパ関数を入れてみます。
defmodule Temp17 do
@behaviour GenServer
def start_link(pname) do
GenServer.start_link(__MODULE__, nil, name: pname)
end
def last(pname) do
GenServer.call(pname, :last)
end
def list(pname) do
GenServer.call(pname, :list)
end
@impl GenServer
def init(_void) do
set_interval()
{:ok, []}
end
defp set_interval() do
Process.send_after(self(), :wakeup, 2 * 1000) # in 2 seconds
end
@impl GenServer
def handle_info(:wakeup, temp_list) do
new_temp_list = append_new(temp_list)
set_interval()
{:noreply, new_temp_list}
end
defp append_new([]) do
[0]
end
defp append_new([head | tail]) do
[head + 1 | [head | tail]]
end
@impl GenServer
def handle_cast(:next, []) do
{:noreply, [0]}
end
@impl GenServer
def handle_cast(:next, [head | tail]) do
{:noreply, [head + 1 | [head | tail]]}
end
@impl GenServer
def handle_call(:last, _from, []) do
{:reply, {:error, []}, []}
end
@impl GenServer
def handle_call(:last, _from, [head | tail]) do
{:reply, {:ok, head}, [head | tail]}
end
@impl GenServer
def handle_call(:list, _from, list) do
{:reply, {:ok, list}, list}
end
いろいろ仕込んでるので長くなってしまってますけど、使っているのは一部です。これを実行するのにも、もう GenServer を書かなくて良くなってます。
iex(2)> Temp17.start_link(:mypid)
{:ok, #PID<0.110.0>}
iex(3)> Temp17.last(:mypid) # start_link 直後に実行
{:error, []}
iex(4)> Temp17.last(:mypid) # わずかに待って実行
{:ok, 0}
iex(5)> Temp17.last(:mypid)
{:ok, 3}
iex(6)> Temp17.list(:mypid) # 暫く経つとリストが延びてる
{:ok, [6, 5, 4, 3, 2, 1, 0]}
せっかく名前が付けられるので並行して、もう一つ動かしてみます。
iex(7)> Temp17.start_link(:hello) # 別のプロセス名でも一つ起動
{:ok, #PID<0.116.0>}
iex(8)> Temp17.last(:hello) # 動いてるか確認
{:ok, 2}
iex(9)> Temp17.last(:mypid) # 最初に動かしたのはどうなってるか
{:ok, 264}
iex(10)> Temp17.list(:mypid) # 放置してたのでリストが長くなってる…
{:ok,
[266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252,
251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237,
236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222,
221, 220, 219, ...]}
iex(11)> Temp17.last(:hello) # 新しい方も順調
{:ok, 13}
iex(12)> Temp17.list(:hello) # 新しい方をリストで確認
{:ok, [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]}
せっかくなので、さらにもう一つ動かしてみましょう。
iex(13)> Temp17.start_link(:world) # もう一つ動かしてみます
{:ok, #PID<0.123.0>}
iex(14)> Temp17.last(:hello) # 2番目のをチェック
{:ok, 101}
iex(15)> Temp17.last(:mypid) # 最初のをチェック
{:ok, 362}
iex(16)> Temp17.last(:world) # 最新のプロセスも動いてます
{:ok, 4}
かなりやりたいことに近づいてきました。ふぅ。
参考文献
Elixir リファレンス GenServer
Elixir リファレンス GenServer の Receiving Regular Messages
変更履歴
2019.02.04 temp16.ex のコピペミスで init 関数が落ちてたのを直しました。 @CostlierRain464 さんからのご指摘。