Elixir GenServer handle_cast 未被调用
Posted
技术标签:
【中文标题】Elixir GenServer handle_cast 未被调用【英文标题】:Elixir GenServer handle_cast not being invoked 【发布时间】:2021-03-03 18:23:36 【问题描述】:我遇到了 Handle Cast 的问题。模块加载后,它调用periodic_comm,每5 秒调用一次save_data 方法。在我的 save_data 方法中,正在打印日志,但该语句什么也不做。
GenServer.cast(__MODULE__, :save_load_data, data)
这里定义了handle cast方法
def handle_cast(:save_load_data, data, state) do
Logger.error("uin saving data====================================")
# state = Map.put_new(state, :load_data, data)
# Logger.debug "updated state : #inspect state"
:noreply, state
end
整个文件是这样的
defmodule Core.Inverter.Manager do
# core inverter process that handles the inverter communication
use GenServer
require Logger
def start_link(_) do
Logger.info("putting some string")
GenServer.start_link(__MODULE__, %, name: __MODULE__)
end
@impl true
def init(_) do
Logger.info("inverter Manager init'ed")
:ok, %configs: nil, inverter_connection: nil, inverter_impl: nil
end
# public api
# initialize the inverter and start periodic comm
def init_inverter(configs) do
GenServer.cast(__MODULE__, :init_inverter, configs)
GenServer.cast(__MODULE__, :start_periodic_comm, configs)
end
def inverter_off() do
GenServer.call(__MODULE__, :inverter_off)
end
@impl true
def handle_cast(:init_inverter, configs, state) do
Logger.debug("initializing the inverter")
# fetching inverter type/configurations
inverter_impl = get_inverter_impl(configs)
# initializing inverter comm channel
inverter_connection = inverter_impl.init(configs)
state = %
configs: configs,
inverter_connection: inverter_connection,
inverter_impl: inverter_impl
Logger.warn("inverter type #state.inverter_impl")
:noreply, state
end
def handle_cast(:start_periodic_comm, configs, state) do
Logger.warn(
"Actually Starting inverter periodic COMM with inverter type #state.inverter_impl"
)
start_periodic_comm(state)
:noreply, state
end
@impl true
def handle_call(
:inverter_off,
_from,
%inverter_impl: inverter_impl, inverter_connection: inverter_connection = state
) do
:ok, inverter_connection = inverter_impl.inverter_off(inverter_connection)
:reply, :ok, %state | inverter_connection: inverter_connection
end
def handle_cast(:save_load_data, data, state) do
Logger.error("uin saving data====================================")
# state = Map.put_new(state, :load_data, data)
# Logger.debug "updated state : #inspect state"
:noreply, state
end
defp start_periodic_comm(state) do
periodic_comm(state)
end
defp periodic_comm(state) do
# call functions from the inverter_type module, e.g infit.ex
receive do
after
5000 ->
:ok, data = state.inverter_impl.get_load_data(state)
save_data(data)
end
periodic_comm(state)
end
# defp save_data() do end
def save_data(data) do
Logger.debug("saving data")
Logger.debug("in savinf data data is : #inspect(data)")
GenServer.cast(__MODULE__, :save_load_data, data)
end
defp get_inverter_impl(configs) do
# add case statements here
Core.Inverter.Infini
end
end
【问题讨论】:
【参考方案1】:我对 elixir 的语法不是很熟悉,但在我看来,periodic_comm 是循环的:
defp periodic_comm(state) do
# call functions from the inverter_type module, e.g infit.ex
receive do
after
5000 ->
:ok, data = state.inverter_impl.get_load_data(state)
save_data(data)
end
periodic_comm(state)
end
不管save_data
的结果如何,线程都在无休止地循环调用periodic_comm
,因此它没有机会接收和执行save_load_data
消息。
为了修复它,您应该重构服务器以使handle_X
中的逻辑不循环,receive
中的所有类型的消息。你可以使用erlang:start_timer
(我不知道长生不老药的对应物)或者依靠gen_server
返回值中的Timeout
来接收超时消息。
【讨论】:
我会使用:timer.send_interval
(erlang.org/doc/man/timer.html#send_interval-2) 发送这样的重复消息。
@PawełObrok 如果计时器数量很大 (erlang.org/doc/efficiency_guide/commoncaveats.html#timer-module),计时器模块的扩展性很差,有时这可能是个问题。 erlang:start_timer
在内部以更可扩展的方式处理计时器。
我会检查并回复您
@JoséM 这是一个很好的观点,但当然取决于您的使用情况。文档说“如果许多进程频繁创建和取消计时器” - 如果您在系统启动时创建恒定数量的计时器,每隔几分钟或其他时间触发一次,那可能不是问题。
@PawełObrok 这是我们的用例,计时器将在启动时创建并在不同模块上以固定频率触发。以上是关于Elixir GenServer handle_cast 未被调用的主要内容,如果未能解决你的问题,请参考以下文章
[Elixir009]像GenServer一样用behaviour来规范接口
在 Elixir ExUnit 中,我如何保证 Supervisor 将创建一个新的 GeNserver?