Идиоматический способ Clojure для создания и управления фоновыми потоками

Что такое idiomatic Clojure способ создания потока, который петли в фоновом режиме делает обновления для некоторых общих ссылок и для управления его жизненным циклом? Я нахожу себя использующим future для этого, но он чувствует себя немного взломанным, так как я никогда не возвращаю значимого значения. Например:.

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

Кроме того, мне нужно быть осторожным с future-cancel, когда фоновая обработка больше не нужна. Любые советы о том, как организовать это в приложении Clojure/Swing, будут приятными. Например. фиктивный 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
          ))

будет жизнеспособным подходом.

Ответ 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 выполняет повторяющиеся задачи в примере приложения колонии ant: Clojure Concurrency