Task.async_stream超时行为

时间:2017-08-24 15:59:34

标签: elixir

Task.async_stream options中,描述了:timeout参数:

  

允许每个任务执行的最长时间(以毫秒为单位)。默认为5000

在我的测试中,我做了以下事情:

iex(8)> Task.async_stream([10, 4, 5], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list
[ok: 10, ok: 4, ok: 5]

iex(10)> Task.async_stream([10], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list      
** (exit) exited in: Task.Supervised.stream(5000)
    ** (EXIT) time out
    (elixir) lib/task/supervised.ex:209: Task.Supervised.stream_reduce/10
    (elixir) lib/enum.ex:1776: Enum.reverse/2
    (elixir) lib/enum.ex:2528: Enum.to_list/1

为什么第一个例子没有超时(但需要约10秒才能执行),而第二个例子表现出预期的超时行为?

1 个答案:

答案 0 :(得分:1)

Task.async_stream的实施从1.4.5变为1.5.1。

让我们来看看会发生什么。

Elixir 1.4.5

在此版本中timeout is part of a receives after block

receive do
  {{^monitor_ref, position}, value} ->
    # ...

  {:down, {^monitor_ref, position}, reason} ->
    # ...

  {:DOWN, ^monitor_ref, _, ^monitor_pid, reason} ->
    # ...
after
  timeout ->
    # ...
end

receive块用于等待来自监视进程发出的生成任务的更新消息。为简单起见,我截断了代码。

这在应用场景中意味着什么? Task.async_stream只有在timeout毫秒的持续时间内没有从生成的任务中收到任何消息时才会超时。

实施例

让我们使用[10, 3, 4]

尝试您的示例
iex> Task.async_stream([10, 3, 4], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list
** (exit) exited in: Task.Supervised.stream(5000)
    ** (EXIT) time out
    (elixir) lib/task/supervised.ex:209: Task.Supervised.stream_reduce/10
    (elixir) lib/enum.ex:1776: Enum.reverse/2
    (elixir) lib/enum.ex:2528: Enum.to_list/1

正如我们所看到的,这会导致超时,正如预期的那样。

现在如果我们尝试使用[10, 5]会怎么样呢?

iex> Task.async_stream([10, 5], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list()
** (exit) exited in: Task.Supervised.stream(5000)
    ** (EXIT) time out
    (elixir) lib/task/supervised.ex:209: Task.Supervised.stream_reduce/10
    (elixir) lib/enum.ex:1776: Enum.reverse/2
    (elixir) lib/enum.ex:2528: Enum.to_list/1

因为看起来初始任务需要太长时间才会超时5秒。但是只要我们添加一个中间步骤,它就会起作用。 1怎么样?

iex> Task.async_stream([10, 5, 1], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list()
[ok: 10, ok: 5, ok: 1]

Elixir 1.5.1

在Elixir 1.5.1中,超时逻辑的工作方式不同。它使用Process.send_after将每个生成的Task的超时消息发送到监视进程。

# Schedule a timeout message to ourselves, unless the timeout was set to :infinity
timer_ref = case timeout do
  :infinity -> nil
  timeout -> Process.send_after(self(), {:timeout, {monitor_ref, ref}}, timeout)
end

此消息是handled in the same receive which spawned the Task and sent the :timeout message

Link to the full function.

实施例

只要一个进程花费的时间超过指定的超时时间,整个流就会按原样进行操作。

iex> Task.async_stream([10, 5, 1], fn i -> :timer.sleep(i * 1000); i end) |> Enum.to_list()
** (exit) exited in: Task.Supervised.stream(5000)
    ** (EXIT) time out
    (elixir) lib/task/supervised.ex:237: Task.Supervised.stream_reduce/7
    (elixir) lib/enum.ex:1847: Enum.reverse/1
    (elixir) lib/enum.ex:2596: Enum.to_list/1

TL; DR

Elixir 1.4.5在从生成的进程中收到结果后重新跟踪超时。 Elixir 1.5.1为每个衍生过程单独跟踪它。