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
的监督进程,然后您可以调用它并告诉 运行 什么代码并监督它。
现在,第三个选项可让您对应用进行更多控制,因为:
- 您可以 运行 任务异步(在后台)而无需 linking 进程指示主管应该执行什么任务和任务进程,而主管将监视此任务
- 您还可以等待结果
- 如果您希望进程在任务崩溃时崩溃,您仍然可以link任务
- 您可以轻松地将任务分发到其他节点。
您可以在 documentation
中找到更多相关信息
我遇到了完全相同的问题,所以我决定给出一个尽可能简单的具体答案。
- 为您的
/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}
]
# ...
- 运行 来自任何地方的任何类型的代码,例如控制器或上下文。
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 来通知。
- 也许还有别的?我很确定这个问题已经在其他地方得到了回答。
我有一个创建 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
的监督进程,然后您可以调用它并告诉 运行 什么代码并监督它。
现在,第三个选项可让您对应用进行更多控制,因为:
- 您可以 运行 任务异步(在后台)而无需 linking 进程指示主管应该执行什么任务和任务进程,而主管将监视此任务
- 您还可以等待结果
- 如果您希望进程在任务崩溃时崩溃,您仍然可以link任务
- 您可以轻松地将任务分发到其他节点。
您可以在 documentation
中找到更多相关信息我遇到了完全相同的问题,所以我决定给出一个尽可能简单的具体答案。
- 为您的
/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}
]
# ...
- 运行 来自任何地方的任何类型的代码,例如控制器或上下文。 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 来通知。
- 也许还有别的?我很确定这个问题已经在其他地方得到了回答。