Периодически вызывая функцию в Clojure

Я ищу очень простой способ периодически вызывать функцию в Clojure.

JavaScript setInterval имеет вид API, который я бы хотел. Если бы я переосмыслил его в Clojure, он выглядел бы примерно так:

(def job (set-interval my-callback 1000))

; some time later...

(clear-interval job)

В моих целях я не возражаю, если это создает новый поток, запускается в пуле потоков или что-то еще. Это не критично, что время тоже точное. Фактически, период, предоставленный (в миллисекундах), может быть просто задержка между окончанием завершения одного вызова и началом следующего.

Ответы

Ответ 1

Также существует довольно много библиотек планирования для Clojure: (от простого до очень продвинутого)

Прямо из примеров главной страницы github at at at:

(use 'overtone.at-at)
(def my-pool (mk-pool))
(let [schedule (every 1000 #(println "I am cool!") my-pool)]
  (do stuff while schedule runs)
  (stop schedule))

Используйте (every 1000 #(println "I am cool!") my-pool :fixed-delay true), если вам нужна задержка секунды между окончанием задачи и началом следующего, а не между двумя запусками.

Ответ 2

Если вы хотите очень просто

(defn set-interval [callback ms] 
  (future (while true (do (Thread/sleep ms) (callback)))))

(def job (set-interval #(println "hello") 1000))
 =>hello
   hello
   ...

(future-cancel job)
 =>true

Прощайте.

Ответ 3

Самый простой подход - просто создать цикл в отдельном потоке.

(defn periodically
  [f interval]
  (doto (Thread.
          #(try
             (while (not (.isInterrupted (Thread/currentThread)))
               (Thread/sleep interval)
               (f))
             (catch InterruptedException _)))
    (.start)))

Вы можете отменить выполнение с помощью Thread.interrupt():

(def t (periodically #(println "Hello!") 1000))
;; prints "Hello!" every second
(.interrupt t)

Вы могли бы просто использовать future для переноса цикла и future-cancel, чтобы остановить его.

Ответ 4

Я забил код, кодирующий это, со слегка измененным интерфейсом, чем указано в исходном вопросе. Вот что я придумал.

(defn periodically [fn millis]
  "Calls fn every millis. Returns a function that stops the loop."
  (let [p (promise)]
    (future
      (while
          (= (deref p millis "timeout") "timeout")
        (fn)))
    #(deliver p "cancel")))

Обратная связь приветствуется.

Ответ 5

Другой вариант - использовать java.util.Timer метод schedualeAtFixedRate

edit - мультиплексные задачи на одном таймере и остановка одной задачи, а не всего таймера

(defn ->timer [] (java.util.Timer.))

(defn fixed-rate 
  ([f per] (fixed-rate f (->timer) 0 per))
  ([f timer per] (fixed-rate f timer 0 per))
  ([f timer dlay per] 
    (let [tt (proxy [java.util.TimerTask] [] (run [] (f)))]
      (.scheduleAtFixedRate timer tt dlay per)
      #(.cancel tt))))

;; Example
(let [t    (->timer)
      job1 (fixed-rate #(println "A") t 1000)
      job2 (fixed-rate #(println "B") t 2000)
      job3 (fixed-rate #(println "C") t 3000)]
  (Thread/sleep 10000)
  (job3) ;; stop printing C
  (Thread/sleep 10000)
  (job2) ;; stop printing B
  (Thread/sleep 10000)
  (job1))

Ответ 6

Вот как я сделал бы версию core.async с каналом остановки.

(defn set-interval
  [f time-in-ms]
  (let [stop (chan)]
    (go-loop []
      (alt!
        (timeout time-in-ms) (do (<! (thread (f)))
                                 (recur))
        stop :stop))
    stop))

И использование

(def job (set-interval #(println "Howdy") 2000))
; Howdy
; Howdy
(close! job)

Ответ 7

Использование core.async

(ns your-namespace
 (:require [clojure.core.async :as async :refer [<! timeout chan go]])
 )

(def milisecs-to-wait 1000)
(defn what-you-want-to-do []
  (println "working"))

(def the-condition (atom true))

(defn evaluate-condition []
  @the-condition)

(defn stop-periodic-function []
  (reset! the-condition false )
  )

(go
 (while (evaluate-condition)
   (<! (timeout milisecs-to-wait))
   (what-you-want-to-do)))