Based on GenServer.reply/2
:
defmodule Test do
use GenServer
def handle_call({:work, item}, from, state) do
IO.puts "Processing item #{item}..."
Process.send_after self(), {:reply, from, item}, 3_000
{:noreply, state}
end
def handle_info({:reply, from, item}, state) do
GenServer.reply from, "Done processing item #{item}!"
{:noreply, state}
end
end
GenServer.start_link(Test, [], name: Test)
Task.start(fn -> IO.puts (GenServer.call Test, {:work, 1}, 5_000) end)
Task.start(fn -> IO.puts (GenServer.call Test, {:work, 2}, 5_000) end)
:timer.sleep(15_000)
.
$ elixir test.exs
Processing item 1...
Processing item 2...
Done processing item 1!
Done processing item 2!
$
So GenServer
doesn’t have to reply to a call immediately. It can store “intermediate results” in its own state - for example when it’s still “waiting” for results from other processes while not being blocked so that it can accept new requests even before the other work is completely done.
The one big drawback to GenServer.call/3
is that it blocks the client process. So when processes “co-operate” it isn’t that uncommon to use GenServer.cast/2
instead by simply including a return pid
(or from
) in the request message proper, for the eventual result message - that way none of the processes ever have to be blocked - they just process the (cast) messages (and results) as they come in.
PS: version that gets the GenServer
state actively involved:
defmodule Test do
use GenServer
def handle_call({:work, item}, from, old_state) do
state = add_result old_state, from, item
IO.puts "Processing item #{item}..."
Process.send_after self(), {:reply, from}, 3_000
{:noreply, state}
end
def handle_info({:reply, from}, old_state) do
{item, state} = pop_result old_state, from
GenServer.reply from, "Done processing item #{item}!"
{:noreply, state}
end
defp add_result(state, from, item),
do: Map.put state, from, item
defp pop_result(state, from) do
item = state[from]
new_state = Map.delete state, from
{item, new_state}
end
end
GenServer.start_link(Test, %{}, name: Test)
Task.start(fn -> IO.puts (GenServer.call Test, {:work, 1}, 5_000) end)
Task.start(fn -> IO.puts (GenServer.call Test, {:work, 2}, 5_000) end)
:timer.sleep(15_000)