从 __module__ 外部调用 Genserver

Bit*_*ise 1 elixir gen-server phoenix-framework

调用函数时,如果找不到所需的数据,我想重试该函数。我想在函数失败后 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
Run Code Online (Sandbox Code Playgroud)

基因服务器:

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
Run Code Online (Sandbox Code Playgroud)

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

输出:

#Reference<0.1408720145.4224712705.56756>
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*yer 5

所以这里有一些需要改进的地方。

首先,您尝试通过注册名称 ( Process.send_after(Servers.Retry...) 访问您的 GenServer,但实际上并未注册该名称。

:name注册名称的基本方法是在您的调用中包含 opt GenServer.start_link,例如:

def start_link(args) do
  GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end
Run Code Online (Sandbox Code Playgroud)

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

模块中使用的原子不需要被其他模块知道,除非它们是 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
Run Code Online (Sandbox Code Playgroud)

我发现这方面是学习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 
Run Code Online (Sandbox Code Playgroud)

现在,它可以重试任何事情——只需将 lambda 传递给它即可。另一方面,这似乎是一个无法对抽象进行评级的功能。在这种情况下,只需将两个模块合二为一即可。我无法为您提供代码示例,因为我不确定 Scheduler 中还有什么,但将它们混合在一起应该不会太复杂。