Elixir Processes and GenServers - Part 2
4. Tasks
Task.start/1
is similar to spawn/1
. It doesn’t return a value, so use it for side-effects.
Task.start(fn -> 1 + 1 |> IO.puts() end)
The Task
module also offers familiar async
and await
functions.
defmodule PlusOne do
def increment(number) do
# random wait
:rand.uniform(1000)
|> Process.sleep()
number + 1
end
end
calc_task = Task.async(PlusOne, :increment, [1])
IO.puts("doing something else")
result = Task.await(calc_task)
IO.puts(result)
A Task
is intended for doing one thing and then finishing. You can start multiple tasks and await the result.
# stream = Task.async_stream(1..10, PlusOne, :increment, [])
1..10
|> Task.async_stream(PlusOne, :increment, [], ordered: false)
|> Enum.map(fn {:ok, num} -> num end)
In this example, Task.async_stream()
is passed a range (list of numbers from 1 to 10). Each number is incremented in a separate process. It returns an Enumberable
of {:ok, result}
tuples. We can use the Enum.map()
to turn this into a list of numbers.
5. Agents
An agent is intended to be longer running and typically maintains state.
{:ok, agent_pid} = Agent.start(fn -> %{} end)
Agent.update(agent_pid, fn state -> Map.put(state, :foo, 1) end)
Agent.update(agent_pid, fn state -> Map.put(state, :bar, 2) end)
foo_value = Agent.get(agent_pid, fn state -> Map.get(state, :foo) end)
IO.puts("foo: #{foo_value}")
full_state = Agent.get(agent_pid, fn state -> state end)
IO.inspect(full_state, label: :state)
If Agent.update
cannot update the agent before a timeout, it will raise an error.
However, Agent.cast
will fire and forget. No error is raised.
Agent.cast(agent_pid, fn state -> Map.put(state, :foo, 7) end)
Agent.get(agent_pid, fn state -> state end) |> IO.inspect(label: :state)
# Let's stop the agent
Agent.stop(agent_pid)
# Now send something to the missing agent.
Agent.cast(agent_pid, fn state -> Map.put(state, :foo, "oops") end)