Elixir 和 Phoenix 的后台进程

Background processes with Elixir and Phoenix

我有一个创建 Question 并将此问题插入数据库的系统,我通过单击我设置的 link 来执行此操作。这是 Phoenix 的直截了当的东西。创建一个控制器动作,为它设置一个 link,然后单击按钮触发该动作。

这暂时有效。但下一阶段是让系统在没有 UI 的任何干预的情况下创建问题。因此,这将我带到 Elixir/Phoenix 的新地方。我的新问题是:我需要在一天中的 x 时间自动 运行 此功能。

问题:

在 Elixir/Phoenix 中实现后台任务最惯用的方法是什么?我对 Genserver 或 Supervisors 知之甚少,但我想我已经准备好开始学习这些工具了。综上所述,您将如何解决将逻辑移动到后台作业的问题。

代码:

  def build_question(conn, _params) do
    case Questions.create_total_points_question(conn) do
      {:ok, _question} ->
        conn
        |> put_flash(:info, "Question created successfully.")
        |> redirect(to: page_path(conn, :index))
      {:error, _msg} ->
        conn
        |> redirect(to: page_path(conn, :index))
    end
  end 

此控制器操作由 link 触发。这段代码需要在后台调用。提前感谢您的帮助。

你的选择不多。

一个是在你的动作中简单 运行 Task.async,但是 links 进程与你生成的进程一起执行你的动作,所以任务崩溃会影响生成的进程它并等待它。顺便说一句,在你的特殊情况下,我认为你不想在你的行动中这样做,因为你在等待结果时没有任何工作要做,所以这是不必要的。

第二种选择,如果您不想等待结果,则使用 Task.start_link。这没关系,但同样作为启动函数的名称,任务进程是 linked 给你的,所以这两个中的任何一个崩溃都会导致其他崩溃。

第三个选项,是使用 Task.Supervisor,只需打开您的应用程序 ex 文件(可能是一个与您项目同名的文件,它位于 lib 文件夹中)并在 children = [... 列表下添加底部像这样

children = [
    ...
    supervisor(Task.Supervisor,[], [name: MyApp.TaskSupervisor])
]

这将在您的应用程序中启动名为 MyApp.TaskSupervisor 的监督进程,然后您可以调用它并告诉 运行 什么代码并监督它。

现在,第三个选项可让您对应用进行更多控制,因为:

  1. 您可以 运行 任务异步(在后台)而无需 linking 进程指示主管应该执行什么任务和任务进程,而主管将监视此任务
  2. 您还可以等待结果
  3. 如果您希望进程在任务崩溃时崩溃,您仍然可以link任务
  4. 您可以轻松地将任务分发到其他节点。

您可以在 documentation

中找到更多相关信息

我遇到了完全相同的问题,所以我决定给出一个尽可能简单的具体答案。

  1. 为您的 /lib/application.ex 添加动态主管。 请参阅文档 here
def start(_type, _args) do
  # List all child processes to be supervised
  children = [
  # ...
  # add the following line
  {Task.Supervisor, name: MyApp.TaskSupervisor}
  ]
  # ...
  1. 运行 来自任何地方的任何类型的代码,例如控制器或上下文。 https://hexdocs.pm/elixir/Task.Supervisor.html#start_child/3
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
  IO.puts "I am running in a task"
end)

请注意,在这种情况下,您不必等待您的任务。这也意味着错误处理由您决定。您可以在执行代码时应用选项(请参阅前面的 link)。默认 :temporary 不会在失败时重新启动任务,其优点是它不会因重复失败而使其父(应用程序)崩溃。


假设您不关心问题构建的结果,现在回答您的具体问题。我会像这样在控制器中实现它:

def build_question(conn, _params) do
  Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
    MyApp.Questions.create_total_points_question(conn)
  end)
  conn
  |> put_flash(:info, "Question is currently being processed.")
  |> redirect(to: page_path(conn, :index))
end

如果你在乎结果,那就不是这样了。在这种情况下,我建议使用 liveview。它可以:

  • 只需监听事件,执行操作,return 准备就绪后立即得到结果(同时阻止用户操作,即使他看到了页面)。我不建议这样做。
  • 执行后台任务并收到 Endpoint 通知。这比听起来容易得多。您基本上是在安装实时视图并确保后台作业发布到端点时进行订阅。
  • 让您的异步任务在完成后向您发送消息。在那种情况下,你会给它一个 pid 来通知。
  • 也许还有别的?我很确定这个问题已经在其他地方得到了回答。