elixir

How to return first async task to complete


I've got several tasks that I'm running asynchronously. Depending on the input, one or more might run long, but only one of the tasks will return the :success message.

slowtask = Task.async(slow())
fasttask = Task.async(fast())

How can I capture the first of the two tasks above to complete, without having to wait for the other? I've tried Task.find/2, but because its implemented with enum, it seems to wait for all exit signals before finding a ref/message. My other thought was to poll this in Stream.cycle, ignoring still alive tasks and catching one that has exited. It seems un elixir like to poll in this way though.


Solution

  • There is no easy way to do this on Elixir yet. Your best option is, if you are only waiting for those messages in a given process, is something like this:

      defmodule TaskFinder do
        def run do
          task1 = Task.async fn -> :timer.sleep(1000); 1 end
          task2 = Task.async fn -> :timer.sleep(5000); 2 end
          await [task1, task2]
        end
    
        # Be careful, this will receive all messages sent
        # to this process. It will return the first task
        # reply and the list of tasks that came second.
        def await(tasks) do
          receive do
            message ->
              case Task.find(tasks, message) do
                {reply, task} ->
                  {reply, List.delete(tasks, task)}
                nil ->
                  await(tasks)
              end
          end
        end
      end
    
      IO.inspect TaskFinder.run
    

    Note you could also use this pattern to spawn tasks in a GenServer and use Task.find/2 to find the matching ones. I have also added this example to Elixir docs.