Elixir阻止了GenServer进程

时间:2015-12-09 13:03:51

标签: elixir blocking gen-server

我有一个简单的GenServer,我希望在其中创建一个每两秒调用一次函数的循环:

defmodule MyModule do
  use GenServer

  def start_link(time) do
    GenServer.start_link(__MODULE__,time)
  end

  #Start loop
  def init(time) do
    {:ok, myLoop(time)}
  end

  #Loop every two seconds
  def myLoop(time) do

    foo = bah(:someOtherProcess, {time})
    IO.puts("The function value was: #{foo}")
    :timer.sleep(2000)
    myLoop(time + 2)
  end
end 

但是当我打电话给:

{:ok, myServer} =MyModule.start_link(time)
IO.puts("Now I can carry on...")

我从未看到上述电话的回复。我觉得这很明显。所以我的问题是,如何在不阻止下游执行任务的过程的情况下创建循环?

感谢。

3 个答案:

答案 0 :(得分:7)

完成您要做的事情的最好/最干净的方法是使用Process.send_after/3。它将超时委托给调度程序,而不是另一个进程。

defmodule MyModule do
  use GenServer

  def start_link(time) do
    GenServer.start_link(__MODULE__,time)
  end

  def init(time) do
    schedule_do_something(time)
    {:ok, %{time: time}}
  end

  def handle_info(:do_something, state) do
    %{time: time} = state
    # do something interesting here
    schedule_do_something(time)
    {:noreply, state}
  end

  defp schedule_do_something(time_in_seconds) do
    Process.send_after(self, :do_something, (time_in_seconds * 1000))
  end
end

GenServer就像一个事件循环,所以为什么要自己重新实现呢?

答案 1 :(得分:2)

由于你在init函数中调用了循环,你的循环无限地阻塞,而init/1回调永远不会返回。

在init上执行操作的常用技术是向GenServer发送消息并使用handle_info/2执行操作。在执行此操作时,您应该记住为handle_info添加一个全部捕获。

defmodule MyModule do
  use GenServer

  def start_link(time) do
    {:ok, pid} = GenServer.start_link(__MODULE__,time)
    send(pid, {:start_loop, time})
    {:ok, pid}
  end

  #Start loop
  def init(time) do
    {:ok, nil}
  end

  def handle_info({:start_loop, time}, state) do
    myLoop(time)
    {:noreply, state}
  end

  #catch all
  def handle_info(_, state) do
    {:noreply, state}
  end    

  #Loop every two seconds
  def myLoop(time) do
    IO.puts("The function value was: #{time}")
    :timer.sleep(2000)
    myLoop(time + 2)
  end
end 

答案 2 :(得分:0)

最好运行另一个处理定时器循环的进程,并让它在执行操作时向您的genserver发送消息。通过这种方式,GenEvent进程通常处于空闲状态以处理其他消息,但是当它应该采取定期操作时会通知它。

您可以通过MyModule.myloop在其自己的流程中运行spawn_link功能来实现此目的。下面是一个例子:

defmodule MyModule do
  use GenServer

  def start_link(time) do
    GenServer.start_link(__MODULE__,time)
  end

  #Start loop
  def init(time) do
    spawn_link(&(MyModule.myloop(0, self))
    {:ok, nil}
  end

  def handle_info({:timer, time}, state) do
    foo = bah(:someOtherProcess, {time})
    IO.puts("The function value was: #{foo}")
    {:noreply, state}
  end 
  def handle_info(_, state) do
    {:noreply, state}
  end

  #Loop every two seconds
  defp myLoop(time, parent) do
    send(parent, {:timer, time})
    :timer.sleep(2000)
    myLoop(time + 2)
  end
end 

如果你不是因为在消息中传递时间而烦恼(你可以根据GenServer状态或类似情况计算它),你可以使用erlang函数:timer.send_interval来发送一个消息每个句点类似于上面的myLoop函数。文档在这里:http://www.erlang.org/doc/man/timer.html#send_interval-2