【问题标题】:Elixir Supervisors — How do you name a Supervised TaskElixir 主管 - 你如何命名受监督的任务
【发布时间】:2015-10-02 11:18:01
【问题描述】:

我真的在和 Elixir 的主管们苦苦挣扎,想知道如何给他们命名以便我可以使用它们。基本上,我只是想启动一个受监督的Task,我可以向它发送消息。

所以我有以下内容:

defmodule Run.Command do
  def start_link do
    Task.start_link(fn ->
      receive do
        {:run, cmd} -> System.cmd(cmd, [])
      end
    end)
  end
end

项目入口点为:

defmodule Run do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Define workers and child supervisors to be supervised
      worker(Run.Command, [])
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Run.Command]
    Supervisor.start_link(children, opts)
  end
end

在这一点上,我什至没有信心自己使用的是正确的东西(特别是Task)。基本上,我想要的只是在应用程序启动时生成一个进程或任务或 GenServer 或任何正确的东西,我可以向其发送消息,这些消息实际上将执行System.cmd(cmd, opts)。我希望这个任务或过程受到监督。当我向它发送{:run, cmd, opts} 消息(例如{:run, "mv", ["/file/to/move", "/move/to/here"]})时,我希望它生成一个新任务或进程来执行该命令。对于我的使用,我什至不需要从任务中取回响应,我只需要它执行即可。任何关于去哪里的指导都会有所帮助。我已经通读了入门指南,但老实说,它让我更加困惑,因为当我尝试做已经完成的事情时,它永远不会像在应用程序中那样。

感谢您的耐心等待。

【问题讨论】:

    标签: elixir erlang-otp erlang-supervisor


    【解决方案1】:

    我会使用 GenServer,设置如下:

    defmodule Run do
      use Application
    
      def start(_, _) do
        import Supervisor.Spec, warn: false
    
        children = [worker(Run.Command, [])]
        Supervisor.start_link(children, strategy: :one_for_one)
      end
    end
    
    defmodule Run.Command do
      use GenServer
    
      def start_link do
        GenServer.start_link(__MODULE__, [], name: __MODULE__)
      end
    
      def run(cmd, opts) when is_list(opts), do: GenServer.call(__MODULE__, {:run, cmd, opts})
      def run(cmd, _), do: GenServer.call(__MODULE__, {:run, cmd, []})
    
      def handle_call({:run, cmd, opts}, _from, state) do
        {:reply, System.cmd(cmd, opts), state}
      end
      def handle_call(request, from, state), do: super(request, from, state)
    end
    

    然后您可以向正在运行的进程发送一个命令来执行,如下所示:

    # If you want the result
    {contents, _} = Run.Command.run("cat", ["path/to/some/file"])
    # If not, just ignore it
    Run.Command.run("cp", ["path/to/source", "path/to/destination"])
    

    基本上我们正在创建一个“单例”进程(只能使用给定名称注册一个进程,并且我们正在使用模块名称注册 Run.Command 进程,因此对start_link 的任何连续调用当进程运行时会失败。但是,这使得设置一个API(run函数)很容易,它可以透明地在另一个进程中执行命令,而调用进程不必知道任何事情。我用@此处为 987654325@ 与 cast,但如果您从不关心结果并且不希望调用进程阻塞,这是一个微不足道的更改。

    对于长时间运行的东西来说,这是一个更好的模式。对于一次性的事情,Task 更简单易用,但我个人更喜欢将GenServer 用于此类全局流程。

    【讨论】:

    • 这是一个完美的解决方案。你不能命名一个任务的原因是如果你想给它发送消息......你不想再使用一个任务了。
    • 感谢您如此详细地解释这一点。这对我很有帮助@bitwalker。感谢您的时间、帮助和耐心。
    • @bitwalker 尝试使用它时,我得到一个 GenServer.call 超时。主管不应该阻止这种事情发生吗? (exit) exited in: GenServer.call(Run.Command, {:run, "ls", ["."]}, 5000) ** (EXIT) time out (elixir) lib/gen_server.ex:356: GenServer .call/3
    • 啊,没关系。 Cast 更适合异步情况,因为同步需要响应时调用更好。对不起!
    • @kkirsche 主管不会阻止超时,它只是确保进程在失败后重新启动,因此您可以在使用 call 时将超时设置为更大的值,如果您正在调用长时间运行的命令,或使用cast :)
    猜你喜欢
    • 2019-04-17
    • 2011-08-07
    • 2014-12-08
    • 2023-03-02
    • 2017-10-29
    • 2015-06-12
    • 2015-09-27
    • 2016-05-27
    • 1970-01-01
    相关资源
    最近更新 更多