Task.async() 可以或应该在 controller#action 中使用吗?

Can or should Task.async() be used in a controller#action?

因为Task.async()链接到当前进程,这是否意味着如果我在控制器的动作中使用它,它会自动被杀死,或者一旦动作被执行就会死亡?

defmodule MyAppWeb.PageController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    
    # ????
    Task.async(fn -> some_10_second_long_stuff() end)
    Task.async(fn -> another_10_second_long_stuff() end)
    # will they both get killed or die?

    render(conn, "index.html")
  end

如果可以,应该用什么代替? Task.start() ?


这 2 个 some_10_second_long_stuff 功能是“发送电子邮件”

Task.async/1 的唯一目的是创建一个可以轻松等待的任务。

除非炸毁或被杀死(阅读:如果它 returns 通常,)它只会静静地死去,并且肯定不会对调用进程做任何事情。如果任务异常终止,也会导致调用进程崩溃。

我在上面链接的文档中都有详细描述。

就是说,如果您只想产生副作用任务,请使用 Task.start/1. If you want the task to be monitored, and restarted, if crashed, use Task.start_link/1。如果您需要控制器进程等待任务,请使用 Task.async/1.

When a process terminates, it will terminate with an exit reason. This exit reason is emitted in an exit signal to all linked processes.

The default behavior when a process receives an exit signal with an exit reason other than normal, is to terminate and in turn emit exit signals with the same exit reason to its linked processes. An exit signal with reason normal is ignored.

因此,仅仅因为一个进程结束,并不一定意味着它的链接进程也会终止。相反,一个进程必须崩溃才能导致其链接的进程崩溃。即使这样,链接进程也可以“捕获退出”以防止其崩溃。

在下面的例子中,一个进程启动了一个新的进程并链接到它,然后原来的进程结束,因为它没有更多的事情要做,即函数没有更多的语句要执行,但是链接的进程继续执行:

defmodule A do

  def start do
    spawn(A, :do_stuff, []) 
  end

  def do_stuff do    #This is the "do_stuff() process"...
    spawn_link(A, :ticker, [10])   #...which spawns a second process, the "ticker()" process, and links to it.
    IO.puts "exiting do_stuff()"   #The "do_stuff()" process ends.  Does the "ticker()" process also end now?
  end

  def ticker(0), do: IO.puts "exiting ticker()" 
  def ticker(num_ticks) do
    :timer.sleep(1000)  #sleep for 1 second
    IO.write "."
    ticker(num_ticks - 1)
  end

end

在 iex 中:

iex(1)> A.start
exiting do_stuff
:ok
..........exiting ticker()
iex(2)> 

输出显示 ticker() 进程在 do_stuff() 进程终止后继续执行 10 秒——看到十个点了吗?

将上面的输出与将 do_stuff() 更改为此时产生的输出进行比较:

 def do_stuff do
    spawn_link(A, :ticker, [10]) 

    :timer.sleep(2000)
    raise "I'm crashing"

    IO.puts "exiting do_stuff() normally"
  end

在 iex 中:

iex(1)> A.start
:ok

..iex(2)> 
23:25:25.772 [error] Process #PID<0.116.0> raised an exception
** (RuntimeError) I'm crashing
    a.ex:20: A.do_stuff/0

这次只有两个点

========= 回复评论:

这里是一个调用 Task.async 到 运行 ticker():

的例子
defmodule A do

  def start do
    spawn(A, :do_stuff, [])
    :ok
  end

  def do_stuff do
    Task.async(A, :ticker, [10])

    #spawn_link(A, :ticker, [10])  #links the process executing do_stuff() and
    #:timer.sleep(2000)
    #raise "I'm crashing"

    IO.puts "exiting do_stuff() normally"
  end

  def ticker(0), do: IO.puts "exiting ticker()"
  def ticker(num_ticks) do
    :timer.sleep(1000)  #sleep for 1 second
    IO.write "."
    ticker(num_ticks - 1)
  end

end

在 iex 中:

iex(1)> A.start
exiting do_stuff() normally
:ok
..........exiting ticker()

注意这10个点,表示在“do_stuff()进程”正常退出后执行了10秒的“ticker()进程”。

任务文档说如果您调用 Task.async(),您 必须 调用 Task.await(),但没有' 似乎是不调用 await() 的惩罚。我猜你会在 Task.async() 完成执行时在调用进程的邮箱中收到一条消息,如果你启动一万亿个任务,那么你可能会溢出邮箱并导致进程崩溃。