从__module__外部呼叫Genserver



调用函数时,如果找不到预期的数据,我想重试该函数。我想在函数失败后的 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

通用服务器:

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菜鸟,所以请原谅缺乏理解。谢谢!!

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

首先,您尝试通过注册名称(Process.send_after(Servers.Retry...)访问您的GenServer,而没有实际注册该名称。

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

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

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

模块中使用的原子不需要被其他模块知道,除非它们是 API 的明确部分(如 opts 和结构)。

我们如何解决它? 简单。 将调用置于Servers.Retry模块内Process.send_after/3

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 传递给它。 另一方面,这似乎是一个可能无法评估抽象的功能。 在这种情况下,只需将两个模块折叠为一个。 我不能给你一个代码示例,因为我不确定调度程序中还有什么,但将它们混合成一个应该不会太棘手。

最新更新