生成和管理后台线程的惯用 Clojure 方式

Posted

技术标签:

【中文标题】生成和管理后台线程的惯用 Clojure 方式【英文标题】:Idiomatic Clojure way to spawn and manage background threads 【发布时间】:2011-07-14 13:38:51 【问题描述】:

什么是惯用的 Clojure 方法来创建一个在后台循环执行更新某些共享 ref 并管理其生命周期的线程?我发现自己为此使用future,但感觉有点像黑客,因为我从不返回有意义的值。例如:

(future (loop [] (do
    (Thread/sleep 100)
    (dosync (...))
    (recur))))

另外,当不再需要后台处理时,我需要小心future-cancel 这个。关于如何在 Clojure/Swing 应用程序中编排它的任何提示都会很好。例如。添加到我的 UI 中的虚拟 JComponent 负责在窗口关闭时杀死线程可能是一个想法。

【问题讨论】:

【参考方案1】:

循环中不需要do;这是暗示的。此外,虽然无条件循环递归没有任何问题,但您也可以使用 (while true ...)。

future 是一个很好的工具;不要让您永远不会获得价值而困扰您。但是,如果您使用代理而不是未来,那应该真的困扰您 - 没有价值观的代理是疯狂的。

但是,谁说你需要future-cancel?只需让您未来的步骤之一是检查它是否仍然需要。然后,您的代码的任何其他部分都不需要跟踪期货并决定何时取消它们。所以像

(future (loop []
          (Thread/sleep 100)
          (when (dosync
                 (alter some-value some-function))
            (recur)) ; quit if alter returns nil
          ))

将是一种可行的方法。

【讨论】:

调用future-cancel 或设置“请取消”标志确实是同样的事情。我仍然需要确保它在正确的时间发生并且稳健地发生。 (我想念RAII) 您关于dowhile 的其他观点当然是正确的。 :) 我不认为我说你应该设置一个请取消标志。如果您的后台任务将在例如一千次迭代之后完成,或者当窗口关闭时,future 可以跟踪这些事情并自行关闭。有些事情比较复杂,需要更细致的管理,对于那些你不妨使用future-cancel。【参考方案2】:

对我来说,将代理用于后台重复任务似乎更整洁

(def my-ref (ref 0))

(def my-agent (agent nil))

(defn my-background-task [x]
  (do
    (send-off *agent* my-background-task)
    (println (str "Before " @my-ref))
    (dosync (alter my-ref inc))
    (println "After " @my-ref)
    (Thread/sleep 1000)))

现在你要做的就是启动循环

(send-off my-agent my-background-task)

my-backgound-task 函数在调用完成后将自身发送给调用代理。

这是 Rich Hickey 在蚁群示例应用程序中执行重复任务的方式:Clojure Concurrency

【讨论】:

这是一个非常糟糕的主意。蚁群是preclojure 1.0。他这样做只是因为那里没有未来。 为什么你认为这是个坏主意?

以上是关于生成和管理后台线程的惯用 Clojure 方式的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 中的惯用错误处理

通过 Clojure 中的集合进行递归的惯用方式

惯用的 Clojure 函数别名

后台惯用写法

“foo = bar || baz”的惯用 Clojure 是啥?

Clojure - 1 个函数的 2 个版本。哪个更惯用?