【问题标题】:How to implement deferred pattern in elixir?如何在 Elixir 中实现延迟模式?
【发布时间】:2017-01-09 19:06:25
【问题描述】:

如何在 Elixir 中实现延迟模式?

让我解释一下它是什么。假设我有一些 fn() 应该在现在之后用 n 秒延迟来实现。但是如果我第二次调用这个fn(),这个函数应该在第二次调用后的n秒内实现,依此类推。也应该有一种方法可以退出这个函数评估。

您可以查看 Lodash 的 _.debounce 函数以供参考。

【问题讨论】:

  • 在我脑海中,你可以用一个简单的 genserver 来实现它。不过也许有更简单的方法。
  • 是的,是的,但我正在寻找更简单的抽象
  • 你不想要一个函数你想要一个进程。 elixir-lang.org/getting-started/processes.html 当进程循环倒计时它的当前值时,它还接收新的传入值 n - 它放在一个列表中,并保持其本地状态。每次倒计时完成后,您继续到列表的下一个成员并再次倒计时 - 同时再次接收。
  • 您创建了一个触发函数调用的请求队列。我猜最简单的版本将使用流和任务。

标签: elixir deferred


【解决方案1】:

一个非常天真和简单的解决方案可以使用原始流程。

defmodule Debounce do
  def start(fun, timeout) do
    ref = make_ref()

    # this function is invoked when we wait for a next application
    recur = fn recur, run ->
      receive do
        ^ref ->
          # let's start counting!
          run.(recur, run)
      end
    end

    # this function is invoked when we "swallow" next applications
    # and wait until we finally apply the function
    run = fn recur, run ->
      receive do
        ^ref ->
          # let's reset the counter
          run.(recur, run)
       after
         timeout ->
           # time is up, let's call it for real & return to waiting
           fun.()
           recur.(recur, run)
       end
    end
    pid = spawn_link(fn -> recur.(recur, run) end)
    fn -> send(pid, ref) end
  end
end

我们来看一个例子

iex> f = Debounce.start(fn -> IO.puts("Hello"), 5000)
iex> f.()
iex> f.()
# wait some time
Hello
iex> f.() # wait some time
Hello

但是,这有很多问题 - 我们的“去抖动”过程实际上永远存在,我们无法取消去抖动,并且可靠性充其量是粗略的。我们可以改进,但我们会失去我们可以调用的简单 fun 的返回值,而是需要调用一个特殊函数来“应用”我们的去抖动器。

defmodule Debounce do
  def start(fun, timeout) do
    ref = make_ref()

    # this function is invoked when we wait for a next application
    recur = fn recur, run ->
      receive do
        {^ref, :run} ->
          # let's start counting!
          run.(recur, run)
        {^ref, :cancel} ->
          :cancelled
      end
    end

    # this function is invoked when we "swallow" next applications
    # and wait until we finally apply the function
    run = fn recur, run ->
      receive do
        {^ref, :run} ->
          # let's reset the counter
          run.(recur, run)
        {^ref, :cancel} ->
          :cancelled
       after
         timeout ->
           # time is up, let's call it for real & return to waiting
           fun.()
           recur.(recur, run)
       end
    end
    pid = spawn_link(fn -> recur.(recur, run) end)
    {pid, ref}
  end

  def apply({pid, ref}) do
    send(pid, {ref, :run})
  end

  def cancel({pid, ref}) do
    send(pid, {ref, :cancel})
  end
end

我们来看一个例子:

iex> deb = Debounce.start(fn -> IO.puts("Hello"), 5000)
iex> Debounce.apply(deb)
iex> Debounce.apply(deb)
# wait some time
Hello
iex> Debounce.apply(deb)
iex> Debounce.cancel(deb)
# wait some time
# nothing

这仍然有一些可能的极端情况 - 生产版本可能会使用 Task 或 GenServer。

【讨论】:

    【解决方案2】:

    为了存储状态,你需要一个进程;一个简单的功能是不够的。为此创建一个流程只是几行代码:

    defmodule Debounce do
      def start_link(f, timeout) do
        spawn_link(__MODULE__, :loop, [f, timeout])
      end
    
      def loop(f, timeout) do
        receive do
          :bounce -> loop(f, timeout)
          :exit -> :ok
        after timeout ->
          f.()
        end
      end
    end
    

    您可以发送此进程:bounce,它会将其超时重置为Debounce.start_link/2 中指定的超时。您也可以发送此进程:exit,它会自行退出而不运行该函数。

    测试:

    f = Debounce.start_link(fn -> IO.inspect(:executing) end, 1000)
    IO.puts 1
    send f, :bounce
    :timer.sleep(500)
    
    IO.puts 2
    send f, :bounce
    :timer.sleep(500)
    
    IO.puts 3
    send f, :bounce
    :timer.sleep(500)
    
    IO.puts 4
    send f, :bounce
    :timer.sleep(2000)
    
    IO.puts 5
    

    输出:

    1
    2
    3
    4
    :executing
    5
    

    【讨论】:

      【解决方案3】:

      好的,这里有一个简化的案例来帮助您: 这里n不是 秒,而是循环步骤,所以你需要很大的n 才能看到任何延迟。这里我使用IO.puts作为调用函数的例子。

      defmodule myModule do
       def loop(list,count) do
         receive do
           n ->  list = list ++ n
      
           :die ->
              Process.exit(self(), :kill )            
         end
         if count == 0 do
           IO.puts( "timeout" )
           [head|tail] = list
           loop(tail, head) 
         else
           loop(list, count-1) 
         end
       end
      end
      

      【讨论】:

      • 以什么方式执行 OP 要求的操作?问题不在于简单的延迟。
      • @michalmuskala 它在每次延迟超时后调用 IO.puts 作为函数示例。当然,您必须先生成进程,然后在需要时发送 n 值的 pid 消息。
      猜你喜欢
      • 2014-11-25
      • 2011-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-03
      • 2014-06-02
      • 1970-01-01
      相关资源
      最近更新 更多