如何返回第一个异步任务来完成

时间:2015-07-23 03:10:13

标签: elixir

我有几个异步运行的任务。根据输入,一个或多个可能会运行很长时间,但只有一个任务将返回:success消息。

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

如何捕获上述两个任务中的第一个完成,而不必等待另一个?我已经尝试了Task.find/2,但由于它是用枚举实现的,所以它似乎在找到ref / message之前等待所有退出信号。我的另一个想法是在Stream.cycle中对此进行轮询,忽略仍然存在的任务并捕获已退出的任务。看来这种灵丹妙药不喜欢以这种方式进行民意调查。

3 个答案:

答案 0 :(得分:8)

还没有简单的方法在Elixir上做到这一点。您最好的选择是,如果您只是在等待给定流程中的那些消息,则是这样的:

  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

请注意,您也可以使用此模式在GenServer中生成任务,并使用Task.find/2查找匹配的模式。我还将此示例添加到Elixir文档中。

答案 1 :(得分:3)

要获得第一个结果,您应该等待消息,然后将消息传递给Task.find/2,并处理{task_result, task}形式的第一个结果。

defmodule Tasks do
  def run do
    :random.seed(:os.timestamp)
    durations = Enum.shuffle(1..10)

    Enum.map(durations, fn(duration) -> Task.async(fn -> run_task(duration) end) end)
    |> get_first_result
    |> IO.inspect
  end

  defp get_first_result(tasks) do
    receive do
      msg ->
        case Task.find(tasks, msg) do
          {result, _task} ->
            # got the result
            result

          nil -> 
            # no result -> continue waiting
            get_first_result(tasks)
        end
    end
  end


  defp run_task(1) do
    :success
  end

  defp run_task(duration) do
    :timer.sleep(duration * 100)
    :ok
  end
end

如果"主人"进程是GenServer,您应该从Task.find/2内调用handle_info/2,而不是运行此递归循环。

答案 2 :(得分:1)

根据Jose Valim的回答,这是我用来匹配回复的回复:

def run do
    task1 = Task.async(fn -> :timer.sleep(10000); :slow end)
    task2 = Task.async(fn -> :timer.sleep(2000); :fail end)
    task3 = Task.async(fn -> :timer.sleep(1000); :fail end)

    await([task1, task2, task3])
end

def await(tasks) do
  receive do
    message ->
      case Task.find(tasks, message) do
        {:fail, task} ->
          await(List.delete(tasks, task))
        {reply, _task} ->
          reply             
        nil ->
          await(tasks)
      end
  end
end

这允许我匹配第一个函数来返回除了:fail atom之外的其他东西,然后给我回复。这是否有效,因为receive / 1只是等待任何消息出现?