Вот что я хотел бы сделать:
(require '[clojure.core.async :as a])
(defn interval [f msecs]
(let [timing (a/chan)
kickoff
#(a/go
(a/<! (a/timeout msecs))
(a/>! timing true))]
(a/go-loop []
(when (a/<! timing)
(a/go (f))
(kickoff)
(recur)))
(kickoff)
#(a/close! timing)))
Пример:
(let [i (interval #(prn "tick") 2000)]
(Thread/sleep 7000)
(i))
;; "tick"
;; "tick"
;; "tick"
;;=> nil
Таким образом, все ваше состояние является локальным и обрабатывается через каналы и идут блоки, а не использовать глобальный атом. Поскольку работа, выполняемая на каждой итерации цикла, более или менее постоянна, ваше фактическое время интервала должно быть достаточно близко к числу msecs
, которое вы проходите.
Или, если вы хотите гарантировать, что разные звонки на f
выполняться в хронологическом порядке, вы могли бы сделать что-то вроде этого, вместо:
(require '[clojure.core.async :as a])
(defn interval [f msecs]
(let [action (a/chan (a/dropping-buffer 1))
timing (a/chan)
kickoff
#(a/go
(a/<! (a/timeout msecs))
(a/>! timing true))]
(a/go-loop []
(when (a/<! action)
(f)
(recur)))
(a/go-loop []
(if (a/<! timing)
(do
(a/>! action true)
(kickoff)
(recur))
(a/close! action)))
(kickoff)
#(a/close! timing)))
(использование этой функции тем же, что и в моей ранней interval
функции.)
Здесь все вызовы f
находятся в тот же цикл. Я использую dropping buffer, поэтому, если вызов f
занимает больше времени, чем msecs
, будущие звонки могут начать падать, чтобы идти в ногу.