handle_info/2 在使用 ErlPort 和 GenServer 投射消息时未收到 Python 发送的消息

handle_info/2 doesn't get the message sent by Python when cast a message using ErlPort and GenServer

我正在尝试使用 ErlPort 将 Elixir 与 Python 混合使用,因此我决定阅读一些有关它的教程以及与所有相关内容相关的文档。我了解逻辑的工作原理以及每个功能的作用。但是,我在发送消息和接收 Python 响应时遇到问题。

根据我阅读的内容和所做的事情,我了解到当我使用 cast_count/1 投射消息时,这是由 handle_cast/2 处理,然后由 Python 处理函数 handle_message(),然后这个使用函数 cast_message() 和从 erlport.erlang 导入的 cast() 投射消息。最后,Elixir 应该用 handle_info/2 处理从 Python 收到的消息。我认为这个功能没有被执行,但我不知道原因,尽管我在不同的来源和 GenServer 和 ErlPort 的文档中调查了很多这些东西。

在我的例子中,我有下一个结构:lib/python_helper.ex 使 ErlPort 工作,lib/server.ex 调用和转换 Python 函数。

lib/python_helper.ex

defmodule WikiElixirTest.PythonHelper do
  def start_instance do
    path =
      [:code.priv_dir(:wiki_elixir_test), "python"]
      |> Path.join()
      |> to_charlist()

    {:ok, pid} = :python.start([{:python_path, path}])
    pid
  end

  def call(pid, module, function, arguments \ []) do
    pid
    |> :python.call(module, function, arguments)
  end

  def cast(pid, message) do
    pid
    |> :python.cast(message)
  end

  def stop_instance(pid) do
    pid
    |> :python.stop()
  end
end

lib/server.ex

defmodule WikiElixirTest.Server do
  use GenServer
  alias WikiElixirTest.PythonHelper

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

  def init(_args) do
    session = PythonHelper.start_instance()
    PythonHelper.call(session, :counter, :register_handler, [self()])

    {:ok, session}
  end

  def cast_count(count) do
    {:ok, pid} = start_link()
    GenServer.cast(pid, {:count, count})
  end

  def call_count(count) do
    {:ok, pid} = start_link()
    GenServer.call(pid, {:count, count}, :infinity)
  end

  def handle_call({:count, count}, _from, session) do
    result = PythonHelper.call(session, :counter, :counter, [count])
    {:reply, result, session}
  end

  def handle_cast({:count, count}, session) do
    PythonHelper.cast(session, count)
    {:noreply, session}
  end

  def handle_info({:python, message}, session) do
    IO.puts("Received message from Python: #{inspect(message)}")
    {:stop, :normal, session}
  end

  def terminate(_reason, session) do
    PythonHelper.stop_instance(session)
    :ok
  end
end

priv/python/counter.py

import time
import sys
from erlport.erlang import set_message_handler, cast
from erlport.erlterms import Atom

message_handler = None


def cast_message(pid, message):
    cast(pid, (Atom('python', message)))


def register_handler(pid):
    global message_handler
    message_handler = pid


def handle_message(count):
    try:
        print('Received message from Elixir')
        print(f'Count: {count}')
        result = counter(count)
        if message_handler:
            cast_message(message_handler, result)

    except Exception as e:
        print(e)
        pass


def counter(count=100):
    i = 0
    data = []
    while i < count:
        time.sleep(1)
        data.append(i+1)
        i = i + 1

    return data


set_message_handler(handle_message)

注意:我删除了@doc以点亮代码片段。是的,我知道 sys 目前没有被使用, Python try 块中的 catch Exception 不是最好的方法,它只是暂时的。

如果我在 iex (iex -S mix) 中测试它,我得到下一个:

iex(1)>  WikiElixirTest.Server.cast_count(19)
Received message from Elixir
Count: 19
:ok

我要注意 call_count/1handle_call/1 工作正常:

iex(3)>  WikiElixirTest.Server.call_count(10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

当我发送消息时,Elixir 与 Python 的通信成功但 Python 与 Elixir 的通信不成功,我做错了什么?

好吧,@Everett 在问题中报告说我在 counter.py 中关闭了错误的原子。

def cast_message(pid, message):
    cast(pid, (Atom('python', message)))

这必须是:

def cast_message(pid, message):
    cast(pid, (Atom('python'), message))

虽然这不能解决问题,但它可以帮助我找到解决方案。修复第一部分 ((Atom('python'), message)) 后,当我执行 cast_count/1 时,我从 Python:

收到一条消息
iex(1)> WikiElixirTest.Server.cast_count(2)
Received message from Elixir
Count: 2
:ok
bytes object expected 

虽然第一次让我有点困惑,因为我在等待诸如“收到来自 Python 的消息:预期的字节对象”之类的消息,由于 handle_info/2。但是,我决定检查 ErlPort 的代码,发现错误在 the line 66 of erlterms.pyAtom class 的一部分。因此错误出现在消息的第一部分,即原子,所以我将其指定为二进制:

def cast_message(pid, message):
    cast(pid, (Atom(b'python', message)))

然后我查了一下:

iex(2)> WikiElixirTest.Server.cast_count(2)
:ok
Received message from Elixir        
Count: 2        
Received message from Python: [[1, 2]]

而且效果很好!所以那里的问题,即使考虑到我对消息元组的错误输入,是 Atom() 需要一个二进制文件。

可能这个误会可能是因为第一个ErlPort tutorial我跟着用了Python2,版本中str是一个字节串,在Python3中它将字符串转换为字节是必要的,因为 str 是一个文本字符串。