【问题标题】:Background processes with Elixir and PhoenixElixir 和 Phoenix 的后台进程
【发布时间】:2021-08-02 16:27:42
【问题描述】:

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

这暂时有效。但下一阶段是让系统在没有任何 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 

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

【问题讨论】:

  • 将用户重定向到其他页面的代码不能异步运行。
  • 明白了。所以,这是需要改变的。但是如何在后台运行 Questions.create_total_points_question?
  • 啊,有趣。我认为这些可能会满足我的需要。

标签: elixir phoenix-framework background-process


【解决方案1】:

你的选择很少。

一种方法是在您的操作中简单地运行Task.async,但是该操作会将执行您操作的进程与您生成的进程关联起来,因此任务崩溃将影响生成它并等待它的进程。顺便说一句,在您的特定情况下,我认为您不希望在您的操作中这样做,因为在等待结果时您没有任何工作可做,因此没有必要。

第二个选项,如果您不想等待结果,请使用Task.start_link。这没关系,但正如启动函数的名称一样,任务进程与您的进程相关联,因此这两个进程中的任何一个崩溃都会导致其他进程也崩溃。

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

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

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

现在,这第三个选项可以让您在应用中获得更多控制权,因为:

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

您可以在documentation找到更多信息

【讨论】:

    【解决方案2】:

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

    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)
    

    请注意,在这种情况下,您不必等待您的任务。这也意味着错误处理取决于您。您可以在执行代码时应用选项(请参阅上一个链接)。默认的: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。它可以:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-22
      • 2023-03-26
      • 1970-01-01
      • 2016-01-17
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多