【问题标题】:Elixir GenServer handle_cast not being invokedElixir GenServer handle_cast 未被调用
【发布时间】: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

【问题讨论】:

    标签: erlang elixir gen-server nerves-project


    【解决方案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 这是我们的用例,计时器将在启动时创建并在不同模块上以固定频率触发。
    猜你喜欢
    • 2019-12-17
    • 2023-03-07
    • 2018-01-13
    • 2017-04-22
    • 2018-12-14
    • 2019-03-27
    • 2017-10-16
    • 1970-01-01
    • 2016-03-14
    相关资源
    最近更新 更多