Elixir 使用主管时启动链接/3的处理结果

Elixir 使用主管时启动链接/3的处理结果,elixir,erlang-supervisor,Elixir,Erlang Supervisor,我设置了一个主管来监督websocket: children = [ %{ id: Slack.Bot, start: {Slack.Bot, :start_link, [MyBot, [], "api_token"]} } ] opts = [strategy: :one_for_one, name: MyBot.Supervisor] Supervisor.start_link(children, opts) MyBot通过websocket发送消息时会收到各种回调

我设置了一个主管来监督websocket:

children = [
  %{
    id: Slack.Bot,
    start: {Slack.Bot, :start_link, [MyBot, [], "api_token"]}
  }
]
opts = [strategy: :one_for_one, name: MyBot.Supervisor]
Supervisor.start_link(children, opts)
MyBot
通过websocket发送消息时会收到各种回调。这很好,但是还有一个额外的回调,我想用它来处理我自己的事件。为了做到这一点,我需要亲自向流程发送消息


我知道我可以从
start\u link/3
的结果中获得PID,但这是由主管自动调用的。如何获取此流程的PID以向其发送消息,同时仍对其进行监控?我必须实现和额外的监控层吗?

您应该使用GenServer来存储PID,然后您可以根据需要引用它。流程应该是这样的:创建一个
MyServer
genserver,它保持您的slackbot PID。然后,在GenServer内部,您可以在call或cast处理程序中执行类似于
send(state.slack,:display_leadboard)
的操作

defmodule MyServer do
  use GenServer

  def child_spec(team_id) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [team_id]},
      type: :worker
    }
  end

  def start_link(team_id) do
    GenServer.start_link(__MODULE__, team_id)
  end

  def init(team_id) do
    {:ok, pid} = Slack.Bot.start_link(MyBot, [], team_id)
    {:ok, %{slack: pid}}
  end

您应该使用GenServer来存储PID,然后可以根据需要引用该PID。流程应该是这样的:创建一个
MyServer
genserver,它保持您的slackbot PID。然后,在GenServer内部,您可以在call或cast处理程序中执行类似于
send(state.slack,:display_leadboard)
的操作

defmodule MyServer do
  use GenServer

  def child_spec(team_id) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [team_id]},
      type: :worker
    }
  end

  def start_link(team_id) do
    GenServer.start_link(__MODULE__, team_id)
  end

  def init(team_id) do
    {:ok, pid} = Slack.Bot.start_link(MyBot, [], team_id)
    {:ok, %{slack: pid}}
  end

您不一定需要PID。Elixir允许使用命名进程,并完全接受名称作为第一个参数。在您的示例中,如果将您的bot命名为
MyBot.Supervisor
,以下内容将成功向其发送消息:

Process.send(MyBot.Supervisor,:message\u to\u bot,[:noconnect])
或者,如果您的机器人运行在不同的节点上:

Process.send({MyBot.Supervisor,:node\u name},:message\u to\u bot,[:noconnect])

一般来说,使用名称而不是PID是Elixir中与发送消息相关的所有内容的常见做法,因为当进程崩溃/重新启动时,PID会发生更改,而名称会永久保留。

您不一定需要PID。Elixir允许使用命名进程,并完全接受名称作为第一个参数。在您的示例中,如果将您的bot命名为
MyBot.Supervisor
,以下内容将成功向其发送消息:

Process.send(MyBot.Supervisor,:message\u to\u bot,[:noconnect])
或者,如果您的机器人运行在不同的节点上:

Process.send({MyBot.Supervisor,:node\u name},:message\u to\u bot,[:noconnect])
一般来说,在Elixir中,使用名称而不是PID是与发送消息相关的所有内容的常见做法,因为当进程崩溃/重新启动时,PID会发生更改,而名称会永久保留。

管理器、PID和启动函数 主管希望启动函数返回以下三个值之一:

{:ok, pid}
{:ok, pid, any}
{:error, any}
在代码中,start函数的最后一个参数默认为空列表

您注意到无法访问pid,因为使用Elixir's会丢失启动函数的结果。在某些情况下,调用它是有意义的,它返回启动子级的pid(以及其他信息,如果有的话)。为确保完整性,还可以使用查询工具查询受监督流程的PID

但是,主管的角色是监督流程,并在必要时重新启动流程。当一个进程重新启动时,它会得到一个新的pid。因此,pid不是长时间引用流程的正确方法

PID和名称 您的问题的解决方案是通过名称引用流程。虚拟机维护进程(以及端口)名称的映射,并允许按名称而不是PID(和端口引用)引用进程(和端口)。注册进程的原语是。大多数函数(如果不是全部的话)都接受一个注册的名称。名称在节点中是唯一的

虽然
spawn*
原语不按名称注册进程,但构建在它们之上的代码通常能够通过启动过程注册名称。这是Slack.Bot.start\u link/4和Supervisor.start\u link/2的情况。通常,这是您的代码通过将
:name
选项传递给
Supervisor.start\u link/2
来完成的。顺便说一句,除非您以后需要参考主管流程,否则这是没有用的,这可能不像您代码中的几位所暗示的那样

Slack.Bot.start\u link/4的情况
为了能够引用您的bot进程,只需确保使用
:name
选项调用
Slack.bot.start\u link/4
,并选择一个名称(原子),例如
MyBot
。这是在子规范中完成的

children = [
  %{
    id: Slack.Bot,
    start: {Slack.Bot, :start_link, [MyBot, [], "api_token", %{name: MyBot}]}
  }
]
opts = [strategy: :one_for_one]
Supervisor.start_link(children, opts)
因此,主管将使用提供的四个参数调用
Slack.Bot.start_link/4
函数(
[MyBot,[],“api_令牌”,[name:MyBot]
)和
Slack.Bot.start_link/4
将使用提供的名称注册进程

如果您选择
MyBot
作为上述名称,您可以通过以下方式向其发送消息:

Process.send(MyBot, :message_to_bot, [])
或使用原语:

send(MyBot, :message_to_bot)
然后由
handle\u info/3
callback处理

作为补充说明,OTP监控树中具有注册名称的进程可能应该基于OTP模块,并让OTP框架进行注册。在OTP框架中,名称注册在初始阶段很早就发生,如果存在冲突,进程将停止,
start\u link
返回错误(
{:error,{:已启动,pid}

Slack.Bot.start\u link/4
确实基于OTP模块:它基于
:websocket\u client
模块,该模块本身基于OTP的
:gen\u fsm
。但是,在its中,它没有将名称传递给
:websocket\u client.start\u link/4
,而是将其传递给
:gen\u fs>
def handle_info({caller, message}, slack, state) do
    ...
    send(caller, result)
end
def call_bot(message) do
    ref = make_ref()
    send(MyBot, {self(), ref, message})
    receive do
        {:reply, ^ref, result} -> {:ok, result}
    after 5_000 ->
        {:error, :timeout}
    end
end
def handle_info({caller, ref, message}, slack, state) do
    ...
    send(caller, {:reply, ref, result})
end