Search code examples
elixirgen-server

How to use Task.await with GenServer?


I am trying to figure out how to start a genserver, and wait for the result until it finishes.

How would I make the server return a result when it exits?

like for example:

defmodule DistanceMatrix do
  use GenServer

  def start id do
    GenServer.start(__MODULE__, id)
  end

  def load() do
    GenServer.cast({:load})
  end

  def handle_cast({:load}, state) do
    # start long process operation
    long_process
    {:noreply, state}
  end

  def long_process do
    :timer.sleep 2000
    %{result: "Process result.."}
  end

end 


results= ids
|> Enum.map(fn id -> DistanceMatrix.start(id) end)
|> Enum.map(&Task.await/1)
|> Enum.map(fn({:ok, result}) -> 
   result
   end)

So, how would I wait and get the result?


Solution

  • Here's one way: in the :load cast, return the result of long_process as the new state. Then add a call which just returns the current state (named :get below). Since a GenServer processes messages sequentially in the order they're sent, the :get call will block until the previous :load is complete.

    defmodule DistanceMatrix do
      use GenServer
    
      def start(id) do
        GenServer.start(__MODULE__, id)
      end
    
      def load(pid) do
        GenServer.cast(pid, {:load})
        pid
      end
    
      def await(pid), do: GenServer.call(pid, :get)
    
      def init(id), do: {:ok, id}
    
      def handle_call(:get, _, state), do: {:reply, state, state}
    
      def handle_cast({:load}, _state) do
        {:noreply, long_process()}
      end
    
      def long_process do
        :timer.sleep(2000)
        %{result: "Process result.."}
      end
    end
    
    1..10
    |> Enum.map(fn id ->
      {:ok, pid} = DistanceMatrix.start(id)
      pid
    end)
    |> Enum.map(&DistanceMatrix.load/1)
    |> Enum.map(&DistanceMatrix.await/1)
    |> Enum.map(fn result ->
      IO.inspect(result)
    end)
    

    Output:

    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    %{result: "Process result.."}
    

    The program takes a little over 2 seconds, as expected.