【问题标题】:Elixir blocked GenServer processElixir 阻止了 GenServer 进程
【发布时间】:2016-03-14 18:27:52
【问题描述】:

我有一个简单的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...")

我从来没有看到上述电话的回报。我猜这很明显。所以我的问题是,如何创建我想要的循环而不阻塞下游执行任务的进程?

谢谢。

【问题讨论】:

    标签: elixir blocking gen-server


    【解决方案1】:

    使用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 就像一个事件循环,那么为什么要自己重新实现呢?

    【讨论】:

      【解决方案2】:

      最好运行另一个处理计时器循环的进程,并让它在应该执行操作时向您的 genserver 发送消息。这样,GenEvent 进程大部分时间都处于空闲状态来处理其他消息,但它会在应该采取周期性行动时得到通知。

      您可以通过在其自己的进程中使用spawn_link 运行您的MyModule.myloop 函数来做到这一点。下面是一个例子:

      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

      【讨论】:

      • 有一个内置函数:timer.send_interval/2 可以满足您的需要,无需自己编写代码。
      【解决方案3】:

      由于您在 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 
      

      【讨论】:

        猜你喜欢
        • 2017-10-16
        • 1970-01-01
        • 1970-01-01
        • 2018-12-14
        • 2014-11-06
        • 2015-06-12
        • 2021-03-03
        • 2017-02-17
        • 2016-05-23
        相关资源
        最近更新 更多