如何从工作进程的 init 中正确 return?

How do I properly return from a worker process's init?

我学习 elixir 才几个星期,我想用它的应用程序模块、Supervisor 和(目前只有一个)worker 来设置一个合适的应用程序。

经过几天的教程、文档、Elixir 论坛和 Whosebug 让我有了这样的理解:

现在,我对最后一部分有疑问。根据我得到的错误,我的工人的 init 函数 return 是 "bad value",但我无法弄清楚 应该 是什么 return supervisor.start_link/2 的值不会失败。

我已经尝试过的值,当init被定义为init(opts):

这是我在行 {:ok, pid} = Supervisor.start_link(MyAppMain.Worker, []):

之后从记录器得到的错误消息
** (Mix) Could not start application myapp: exited in: MyAppMain.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:bad_return, {MyAppMain.Worker, :init, {:ok, %{}}}}}
            lib/MyAppMain.ex:15: MyAppMain.start/2
            (kernel) application_master.erl:273: :application_master.start_it_old/4

那么,Supervisor.start_link/2 接受哪些 return 值?

编辑:一些实际代码

应用模块:

defmodule MyAppMain do
  use Application

  def start(_type, _args) do
    {:ok, pid} = Supervisor.start_link(MyAppMain.Worker, [])
  end
end

工作模块:

defmodule MyAppMain.Worker do
  def child_spec(opts) do
    %{
       id: MyAppMain.Worker,
       start: {__MODULE__, :start_link, []},
       restart: :transient,
       type: :worker
     }
  end

  def start_link(state) do
    do_work() # returns a "fn() -> nil end", because "expected a function"
  end

  def init(opts) do
    {:ok, %{}} # Also tried putting the elements of the above list here.
  end

  defp do_work()
    #do some work

    if(prompt_restart()) do
      do_work()
    else
      fn() -> nil end
    end
  end

  defp prompt_restart() do
    # prompt the user whether to repeat the task via IO.gets, return either true or false
  end
end

问题在于您设置应用程序的方式。您将您的应用程序视为主管(有点像,但不像您在使用它),并且您的 Worker 的 start_link 部分被错误使用。该应用程序实际上只是用来启动一些顶级主管,它们自己启动一些工人。这是一个基本示例:

申请:

defmodule Chat do
  use Application

  def start(_type, _args) do
    Chat.Supervisor.start_link(name: Chat.Supervisor)
  end
end

主管(由应用程序启动)

defmodule Chat.Supervisor do
  def start_link(state) do
    Supervisor.start_link(__MODULE__, nil, named: __MODULE__)
  end

  def init(opts) do
    children = [
      %{
        id: Chat.RoomWorker,
        start: {Chat.RoomWorker, :start_link, []},
        restart: :transient,
        type: :worker
     }
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

Worker(由 Supervisor 启动)

defmodule Chat.RoomWorker do
  use GenServer
  def start_link() do
    GenServer.start_link(__MODULE__, nil, named: __MODULE__)
  end

  def init(opts) do
    {:ok, []}
  end
end

start_link 在成功时调用 return {:ok, pid}init 函数发生在生成进程中,它 return 一些 {:ok, state} 响应让生成器知道已成功启动。 start_link 东西在调用者进程中运行,init 代码在新生成的进程中运行。

尝试重构您的代码,使应用程序启动顶级主管,并且该主管正在启动一些工作进程。虽然您可以从 Application 启动 worker 而放弃 Supervisor,但最好放弃 Application 并只使用 Supervisor,但最好看看这三者如何协同工作。