5
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 5 years have passed since last update.

はじめてなElixir(18) Taskにも触ってみる

Last updated at Posted at 2018-10-07

こうなったらタスクもやってみましょう。
はじめてなElixir(17) で Agent を使ってみました。プログラミングElixirに出てくる残りの並行プロセス技法は Task です。今日はあまりハマらずで短いです。

なお、プログラミングElixirではAgentより扱いが軽いですが、リファレンス読むとまあまあ他のプロセス系なモジュールと同じぐらいの分量があります。

#前回でいいわすれたこと 

備忘録ですが Agent 名は大文字で始めます。これ、裏で勝手に :Elixir が補われるらしく Foo と書くと :Elixir.Foo というアトムになるそうな。この中途半端で微妙なおせっかい、Elixir らしいなあと思います。

#勝手に走ってて問い合わせに応答もするプロセスを作る
Agent は自立して走りますが、メッセージを与えるたびに関数が走るので、勝手に何かやってるという感じではありません。(その意味では Agent ってネーミングはあまり適切ではないかもしれません。私が技を知らないだけかもしれませんが)
たとえば前回のエージェントは定期的に(例えば1秒ごとに)関数を起動して状態を変える(リストを伸ばす)ということはしません。

タスクでエージェントを刺激する

これ、毎秒 cast なり update なりを叩けばできます。人間がやらなくても別のプロセスがやってくれれば良いですね。

temp12.ex
defmodule Temp12 do
  def tick(agent, interval \\ 1000) do
    cast(agent)
    :timer.sleep(interval)
    tick(agent, interval)
  end

  def start(agent) do
    Agent.start(fn -> [0] end, name: agent)
  end
  
  def cast(agent) do
    Agent.update(agent, __MODULE__.succ())
  end

  def get(agent) do
    Agent.get(agent, &(&1))
  end

  def succ() do
    fn(l) -> [hd(l) +1 | l] end
  end
end

tick という関数は指定時間ごとに agent の cast を叩くので、これを非同期に起動してやれば望みのものができそうです。

iex.ex
iex(1)> Temp12.start(Foo) # Agent Foo を動かします
{:ok, #PID<0.106.0>}
iex(2)> Temp12.get(Foo) # Foo の持ってる初期値を表示
[0]
iex(3)> task=Task.async(fn -> Temp12.tick(Foo) end) # 非同期の task を立ち上げます。Foo を刺激します。
%Task{
  owner: #PID<0.104.0>,
  pid: #PID<0.109.0>,
  ref: #Reference<0.906436406.773062658.108199>
}
iex(4)> Temp12.get(Foo) # Foo の状態リストが伸び始めました
[3, 2, 1, 0]
iex(5)> Temp12.get(Foo) # 待ってるとずっと伸びます
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
iex(6)> 

とまあ Task と Agent でよろしくやってくれます。こういう、ちょこちょこと書いて組み合わせて合成するのは楽しいです。

Task でもう少し遊ぶ

インターバルタイマでちょっとずつリストを作るというのは、凝ったことをしないで良いなら Stream.interval でできます。

iex.ex
iex(1)> Stream.interval(1000) |> Enum.take(5)            
[0, 1, 2, 3, 4]

これ、結果が出てくるのに5秒ほど待たされます。

これに Task の async_stream というのをひっつけます。関数はもらった要素をそのまま返すようにしておきます。

iex(2)> task = Stream.interval(1000) |> Task.async_stream(&(&1)) 
#Function<1.95549051/2 in Task.build_stream/3>

これでバックグラウンドで task が走ります。

iex(3)> Enum.map(task, &(IO.inspect(&1)))
{:ok, 0}
{:ok, 1}
{:ok, 2}
{:ok, 3}
{:ok, 4}
{:ok, 5}
{:ok, 6}

Enum.map を使って task の結果を見ると毎秒出力がはじまります。全部 :ok をつけてよこしてるのが分かります。だから何だって言われそうですが、わざわざ非同期プロセスを噛ましてストリームしてみたというところです。

Task は途中でなにかやり取りするような関数がなさそうですし、Agent はずっと何か勝手にやり続けるようなことがなさそうですし、やりたいことはどっちかだけじゃ実現できそうにないようです。

あと、Task は Agent と違って async や start のときに name: で名前を付けられませんでした。

#参考文献
漁るといろいろ出てくるので「車輪の再発明」ならぬ「車輪の再発見」を続けてるような気分になります。でもまあ、打って、エラーして、くじけて、調べて、やり直して… しないと身体が覚えないのでね。すべてのエンジニアが車輪の再発見をしてるものと信じてます。

5
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
5
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?