从__module __调用Genserver

时间:2018-01-29 21:50:07

标签: elixir phoenix-framework gen-server

调用函数时,如果我找不到预期的数据,我想重试该函数。我希望在函数失败10秒后重试。

当前的实施:

计划

def check_question do
    case question = Repo.get_by(Question, active: true, closed: false) do

    question when not(is_nil(question)) ->
      case ActiveQuestion.ready_for_answer_status(question) do
        n when n in ["complete", "closed"] ->
          question
            |> Question.changeset(%{ready_for_answer: true, closed: true})
            |> Repo.update()
      end
    _ ->
      Process.send_after(Servers.Retry, :update, 10_000)
    end
  end

GENSERVER:

defmodule Servers.Retry do
  use GenServer
  require IEx

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end

  def init(state) do
    {:ok, state}
  end

  def handle_info(:update, state) do
    Scheduler.check_question()
    {:noreply, state}
  end
end

正如您所看到的,如果不满足case语句,我会尝试重试该函数。但这不太有效。

输出:

#Reference<0.1408720145.4224712705.56756>

它从不在Servers.Retry中调用genserver。我是一个超级GenServer noob,所以原谅缺乏理解。谢谢!!

1 个答案:

答案 0 :(得分:4)

因此,有一些事情需要改进。

首先,您尝试按注册名称(Process.send_after(Servers.Retry...)访问GenServer,而不实际注册名称。

注册姓名的基本方法是在:name的通话中加入GenServer.start_link opt,例如:

def start_link(args) do
  GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end

接下来,从设计角度来看,您已经破坏了Retry GenServer的封装。作为一个快速的经验法则:

  

模块中使用的原子不应该被其他模块所知,除非它们是API的明确部分(如opts和structs)。

我们如何解决?简单。将调用Process.send_after/3放在Servers.Retry模块中:

defmodule Servers.Retry do
  use GenServer

  ### External API:

  def start_link do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def retry(delay \\ 10_000) do
    Process.send_after(__MODULE__, :retry, delay)
  end

  ### GenServer Callbacks

  def init(state) do
    {:ok, state}
  end

  def handle_info(:retry, state) do
    Scheduler.check_question()
    {:noreply, state}
  end
end

我发现这方面是学习GenServer最令人困惑的部分之一:这个模块中定义的一些代码在GenServer进程中运行,有些在其他进程中运行。具体来说,这两个API方法旨在由其他进程调用 - 由主管start_link调用,而retry由实际客户调用。

通过将Process.send_after调用置于API方法中,我们简化了其他方法,并将 Retry服务器所执行的内容(再次尝试)从中解耦如何实现(使用send_after)。

我的最后一个建议:使重试服务器更通用,或更具体。现在,它只能帮助Scheduler,因为它太具体了 - 重试的动作是硬编码的。一个想法是重试接受arity-0函数在需要重试时调用:

def retry(action, delay \\ 10_000) do
  Process.send_after(__MODULE__, {:retry, action}, delay)
end
# ...snip
def handle_info({:retry, action}, state) do
  action.()
  {:noreply, state}
end 

现在,它可以重试任何事情 - 只需将lambda传递给它。另一方面,这似乎是一个可能无法评估抽象的功能。在这种情况下,只需将两个模块折叠成一个。我不能给你一个代码示例,因为我不确定Scheduler中还有什么,但是将它们混合成一个不应该太过于欺骗。