从 __module__ 外部调用 Genserver
Call Genserver from outside of the __module__
调用函数时,如果找不到预期的数据,我想重试该函数。我想在函数失败后 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 菜鸟,所以请原谅我缺乏理解。谢谢!!
所以这里有一些地方需要改进。
首先,您试图通过注册名称 (Process.send_after(Servers.Retry...
) 访问您的 GenServer,但实际上并未注册该名称。
注册名称的基本方法是在您对 GenServer.start_link
的调用中包含 :name
选项,例如:
def start_link(args) do
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end
接下来,从设计的角度来看,您破坏了 Retry
GenServer 的封装。作为一个快速的经验法则:
The atoms used within a module shouldn't need to be known by other modules unless they are explicity part of the API (like opts and 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 方法中,我们简化了其他方法,并分离了 what 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 中还有什么,但是将它们混合成一个应该不会太骗人。
调用函数时,如果找不到预期的数据,我想重试该函数。我想在函数失败后 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 菜鸟,所以请原谅我缺乏理解。谢谢!!
所以这里有一些地方需要改进。
首先,您试图通过注册名称 (Process.send_after(Servers.Retry...
) 访问您的 GenServer,但实际上并未注册该名称。
注册名称的基本方法是在您对 GenServer.start_link
的调用中包含 :name
选项,例如:
def start_link(args) do
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end
接下来,从设计的角度来看,您破坏了 Retry
GenServer 的封装。作为一个快速的经验法则:
The atoms used within a module shouldn't need to be known by other modules unless they are explicity part of the API (like opts and 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 方法中,我们简化了其他方法,并分离了 what 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 中还有什么,但是将它们混合成一个应该不会太骗人。