Elixirで関数の非同期呼び出しを行って結果に対して逐次的に別の処理をかける
はじめに
Elixirで非同期をやる場合Task.async
をつかいますが、非同期で呼び出した複数の関数の呼び出し結果に対して、逐次的に別の処理をかけたい状況で、同僚のElixirに詳しい人のコード見てなるほどと思ったのでメモとして残しておきます。
どうするか?
具体的に下記の手順を踏みます。
- 関数のリストを作成する
- 作成したリストに
Enum.map
でTask.async
を呼びだす Enum.each
等でasyncされたタスクのリストに対してTask.await
を呼び出して結果に対して処理を行なう
実装してみる
まずは非同期で呼び出される関数の方を実装します。
defmodule Chores.FirstTask do def do_something do :timer.sleep(3000) IO.puts "finised first task after wating 3s" "sleeped 3s" end end defmodule Chores.SecondTask do def do_something do :timer.sleep(2000) IO.puts "finised second task after wating 2s" "sleeped 2s" end end
Chores.FirstTask.do_somethihg/0
は最初に呼び出されることを想定した関数です。開始時に3秒待ち、"sleeped 3s"
の文字列を返します。
Chores.SecondTask.do_somethihg/0
は二番目に呼び出されることを想定した関数で、Firstと違って2秒待ってから"sleeped 2s"
の文字列を返します。
次はこれらの関数を非同期で呼び出すところを実装します。
defmodule AsyncExample do alias Chores.{FirstTask, SecondTask} def do_tasks do [ fn -> FirstTask.do_something end, fn -> SecondTask.do_something end, ] |> Enum.map(&Task.async(&1)) |> Enum.each(fn t -> Task.await(t) |> IO.puts end) end end
このAsyncExample.do_task/0
は基本的には前述の手順通りです。
最後のIO.puts
でそれぞれの関数の返り値を標準出力に出力しています。
実行する
async_example.exs
に先程のモジュールを定義して実行してみます。
$ time elixir async_example.exs finised second task after wating 2s finised first task after wating 3s sleeped 3s sleeped 2s real 0m3.260s user 0m0.573s sys 0m0.066s
finised second task after wating 2s
が先に出力されていることと、実行時間が約3秒であることからタスクが非同期に実行されていることがわかります。
その後、それぞれの関数が返すsleeped 3s
とsleeped 2s
が出力されます。ここで遅れて実行が完了するsleeped 3s
のほうが先に出力されるのはEnum.each
の処理順はあくまで関数のリストの順番だからだと思います。
結果をエラーハンドリングしたい場合
実行したタスクのエラーハンドリングを行いたい場合はEnum.each
の代わりにEnum.while_reduce
を使って行なうことができます。Enum.while_reduce
を用いたエラーハンドリングについては前にブログを書いたのでそちらを良ければ見てください。