このコラムはページ上で非同期処理の実装を検討します
- assign_async
- Task.start_link
を検討したいと思います
実行イメージ
assign_async
Task.start_link
処理イメージ(共通)
- 実行ボタンを押す
- 実行後実行ボタンを押せなくする
- 処理中を再現する
- GUIと別のプロセスにする
- 擬似的に進行中を作る
- GUI側のプロセスに進捗を通知する
- 完了時通知
- GUI側のプロセスに完了通知する
- 実行後実行ボタンを押せるようにする
サンプルソース
assign_async
lib/live_async_web/live/index.ex
defmodule LiveAsyncWeb.SampleLive.Index do
use LiveAsyncWeb, :live_view
def mount(_params, _session, socket) do
socket =
assign(socket, text: "実行ボタンを押してください")
|> assign(btn: true)
|> assign(ret: nil)
{:ok, socket}
end
def handle_event("start", _, socket) do
pid = self()
socket =
assign(socket, btn: false)
|> assign(ret: nil)
|> assign_async(:ret, fn -> run(pid) end)
{:noreply, socket}
end
def run(pid) do
1..100
|> Enum.each(fn x -> run_sub(x, pid) end)
Process.send(pid, {:end, "完了しました"}, [])
{:ok, %{ret: Enum.random(1..10)}}
end
def run_sub(no, pid) do
Process.sleep(20)
Process.send(pid, {:run, "処理中#{no} %"}, [])
end
def handle_info({:run, msg}, socket) do
{:noreply, assign(socket, text: msg)}
end
def handle_info({:end, msg}, socket) do
socket =
assign(socket, text: msg)
|> assign(btn: true)
{:noreply, socket}
end
def render(assigns) do
~H"""
<Layouts.flash_group flash={@flash} />
<div class="p-5">
<button disabled={!@btn} class="btn" phx-click="start">実行</button>
<p class="m-2">{@text}</p>
<p class="m-2">{inspect(@ret)}</p>
</div>
"""
end
end
- def handle_event("start", _, socket)
- ボタンを押された時
- assign_async(:ret, fn -> run(pid) end) で非同期処理
- pidはGUI側のpid
- def run(pid)
- 100回run_sub(no, pid)を呼ぶ
- 完了時にProcess.send(pid, {:end, "完了しました"}, [])
- GUI側に通知します
- def run_sub(no, pid)
- 20ミリ待つ
- 処理中を再現でProcess.send(pid, {:run, "処理中#{no} %"}, [])
- GUI側に通知します
- def handle_info({:run, msg}, socket)
- 処理中の通知を受け取る
- def handle_info({:end, msg}, socket)
- 完了時の通知を受け取る
Task.start_link
lib/live_async_web/live/async.ex
defmodule LiveAsyncWeb.AsyncTest do
use LiveAsyncWeb, :live_view
def render(assigns) do
~H"""
<Layouts.flash_group flash={@flash} />
<div class="p-5">
<button disabled={!@btn} class="btn" phx-click="start">実行</button>
<p class="m-2">{@text}</p>
</div>
"""
end
def mount(_params, _session, socket) do
socket =
assign(socket, text: "実行ボタンを押してください")
|> assign(btn: true)
{:ok, socket}
end
def handle_event("start", _unsigned_params, socket) do
pid = self()
Task.start_link(fn -> run(pid) end)
socket = assign(socket, btn: false)
{:noreply, socket}
end
def run(pid) do
1..100
|> Enum.each(fn x -> run_sub(x, pid) end)
Process.send(pid, {:end, "完了しました"}, [])
:ok
end
def run_sub(no, pid) do
Process.sleep(20)
Process.send(pid, {:run, "処理中#{no} %"}, [])
end
def handle_info({:run, msg}, socket) do
socket = assign(socket, text: msg)
{:noreply, socket}
end
def handle_info({:end, msg}, socket) do
socket =
assign(socket, text: msg)
|> assign(btn: true)
{:noreply, socket}
end
end
差分
- def handle_event("start", _unsigned_params, socket) do
- Task.start_link(fn -> run(pid) end)
ぐらいです
厳密にはassign_asyncの方は直接実行結果を渡すことが出る
どっちを使う?
- assign_asyncで良いのでは
- そもそも非同期そんなに長い処理書きませんよね
- ベージ閉じても実行したい場合はこの2つの方法以外を考える必要がある
- assign_asynが使えないバージョンならTask.start_linkで再現もありかも