16

What is the idiomatic Clojure way to create a thread that loops in the background doing updates to some shared refs and to manage its lifetime? I find myself using future for this, but it feels like a little bit of a hack as I never return a meaningful value. E.g.:

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

Also, I need to be careful to future-cancel this when the background processing is no longer needed. Any tips on how to orchestrate that in a Clojure/Swing application would be nice. E.g. a dummy JComponent that is added to my UI that is responsible for killing the thread when the window is closed may be an idea.

pauldoo
  • 18,087
  • 20
  • 94
  • 116

2 Answers2

9

You don't need a do in your loop; it's implied. Also, while there's nothing wrong with an unconditional loop-recur, you may as well use (while true ...).

future is a fine tool for this; don't let it bother you that you never get a value back. That should really bother you if you use an agent rather than a future, though - agents without values are madness.

However, who said you need to future-cancel? Just make one of the steps in your future be to check whether it's still needed. Then no other parts of your code need to keep track of futures and decide when to cancel them. So something like

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

would be a viable approach.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Calling `future-cancel` or setting a "please cancel" flag amount to the same thing really. I still need to make sure it happens at the correct time and robustly so. (I miss RAII) – pauldoo Mar 13 '11 at 20:54
  • Your other points about `do` and `while` are of course correct. :) – pauldoo Mar 13 '11 at 20:55
  • I don't think I said you should set a please-cancel flag. If your background task will be done after, say, a thousand iterations, or when the window has been closed, the future can track those things and shut itself down. Some things are more complicated and require more detailed management, and for those you might as well use future-cancel. – amalloy Mar 13 '11 at 23:17
0

Using agents for background recurring tasks seems neater to me

(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)))

Now all you have to do is to initiate the loop

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

The my-backgound-task function is sending itself to the calling agent after its invocation is done.

This is the way how Rich Hickey performs recurring tasks in the ant colony example application: Clojure Concurrency

mamciek
  • 37
  • 4