The Many uses of Elixir’s Task Module

The  Task module is an amazing tool packed with functionality that can both improve the performance of our application and be used as a part of our system architecture. This article is all about exploring the different ways   Task  can be used and why we would want to use it in the first place.

Using the Task module for Parallelization

In a lot of languages, setting a bunch of heavy tasks to run in parallel isn’t usually the simplest of things, however, in Elixir, it’s no more complex than wrapping our function with  Task.async_stream.

For example, take a long-running task

def func(i) do
  Process.sleep(:timer.seconds(i))
end

Running this function sequentially would take around 55 seconds if we were to do

Enum.each(1..10, &func/1)

however, with a simple swap, we can bring this time down to 10 seconds or a 5x reduction in time to complete with nothing more than a change of the function used to iterate.

Using the Task module to start side effects

There are plenty of cases in Elixir we still need side effects, for example perhaps we get some input from a client and we need to update the database, but don’t want to wait for that update to return a response, this can easily be done by Task.start. However, what if we want the task to be restarted on failure? This is a job for a  Task.Supervisorwhich can have customized restart options and also keeps the Task under supervision so that it can be restarted on failure. If it crashes fully the supervisor can handle its exit gracefully, which removes the chance of it leaving dangling processes.

To use the Task.Supervisormodule we must first start the task supervisor to our application module

defmodule MyApp.Application do
  use Application
  def start(_type, _args) do
    children = [{Task.Supervisor,
      name: Task.SomeThingSupervisor,
      # this will cause our tasks to restart on non normal exit
      restart: :transient
    }]
    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

Using Task.async to speed up multiple long synchronous functions

Occasionally, we have multiple functions that are long-running and still require the response of them all for our final response. Take a case where we need to get multiple aggregates and aggregate those or even just return them, we could have a function like this.

defmodule SomeServer do
  use GenServer
  @default_name :some_server
  def start_link(opts \\ []) do
    opts = Keyword.put_new(opts, :name, @default_name)
    GenServer.start_link(SomeServer, %{}, opts)
  end
  def init(state) do
    # Make sure Task.MySupervisor is created in Application
    Task.Supervisor.async_nolink(Task.MySupervisor, fn ->
      Process.sleep(1000)
      {:set_state, :first, "First"}
    end)
   Task.Supervisor.async_nolink(Task.MySupervisor, fn ->
      Process.sleep(2000)
      {:set_state, :second, "Second"}
    end)
    {:ok, state}
  end
  def get_state, do: :sys.get_state(@default_name)
  def handle_info({_ref, {:set_state, key, new_state}}, state) do
    {:noreply, Map.put(state, key, new_state)}
  end
end

Mein Text  Mein Code weiter geht’s…

Autor

Thomas Hadorn
Thomas Hadorn
Thomas is the inventor of the first time machine, built out of a DeLorean sports car.

Despite being intelligent and logical for the most part, Thomas is somewhat naive at times about the unknown possible uses of his time machine, initially actively explores the course of the world's future and tries to alter the past or future of the people to improve their lives.

However, events lead him to conclude that time travel is too dangerous for humankind. His conviction initially strengthened when he realizes that he has unwittingly altered history by preventing the death of Clara Clayton in 1885; he concludes that the time machine has "caused nothing but disaster".