在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秒才能执行),而第二个例子表现出预期的超时行为?
答案 0 :(得分:1)
Task.async_stream
的实施从1.4.5变为1.5.1。
让我们来看看会发生什么。
在此版本中timeout is part of a receive
s 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中,超时逻辑的工作方式不同。它使用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。
只要一个进程花费的时间超过指定的超时时间,整个流就会按原样进行操作。
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
Elixir 1.4.5在从生成的进程中收到结果后重新跟踪超时。 Elixir 1.5.1为每个衍生过程单独跟踪它。