Clojure中惯用的方式来产生和管理后台线程

16

在Clojure中创建一个在后台循环执行更新共享引用并管理其生命周期的线程,有没有惯用的方式?我发现自己在使用future实现这个功能,但感觉有点像是一种不太正式的hack,因为我从来没有返回过有意义的值。例如:

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

同时,我需要小心地在后台处理不再需要时进行future-cancel。在Clojure/Swing应用程序中如何编排这一点的任何提示都将是不错的选择。例如,在我的UI中添加一个虚拟的JComponent,负责在窗口关闭时终止线程可能是一个好主意。

2个回答

9
你的循环中不需要使用do,因为它是隐含的。同时,无条件的loop-recur也没有问题,但你可以使用(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) - pauldoo
你关于 dowhile 的其他观点当然是正确的。 :) - pauldoo
我不认为我说过你应该设置一个请取消标志。如果您的后台任务将在一千次迭代之后完成,或者当窗口已关闭时,未来可以跟踪这些事情并关闭自身。有些事情更加复杂,需要更详细的管理,对于这些事情,最好使用future-cancel。 - amalloy

0

对我来说,使用代理来处理后台重复任务更加整洁

(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-background-task 函数在调用完成后会将自己发送给调用代理。

这是 Rich Hickey 在蚂蚁群落示例应用中执行循环任务的方式: Clojure 并发


那是一个非常糟糕的想法。这个蚂蚁群体是在Clojure 1.0之前创建的。他之所以这样做,是因为当时没有未来(futures)技术可用。 - nickik
4
你认为这是个不好的想法的原因是什么? - Maciej

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接