2014-01-28 6 views
16

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

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

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

; some time later... 

(clear-interval job) 

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

+0

возможно дубликат [Исполнительной кода на регулярной основе определенные интервалы времени в Clojure] (http://stackoverflow.com/questions/8220347/executing-code-at-regularly- timed-interval-in-clojure) –

ответ

20

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

Прямо из примеров GitHub домашней страницы на-на:

(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), если вам нужна задержка секунды между окончанием задачи и началом следующего, а не между двумя запусками.

+0

Я видел два из них. Моя потребность очень проста. Не могли бы вы привести примеры их использования для этого сценария? –

+2

Я отредактировал этот вопрос несколько дней назад, добавив еще одну библиотеку: chime (https://github.com/james-henderson/chime). Я не вижу своих прав, так что либо он потерялся, либо консервирован? – fmjrey

8

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

(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, чтобы остановить его.

+0

Как это не дает вам двусмысленной ошибки конструктора в третьей строке? В Intellij мне пришлось набирать текст с помощью '^ Runnable'. – Carcigenicate

+0

@Carcigenicate Я просто попробовал его в REPL ('Leiningen 2.6.1 на Java 1.8.0_45 Java HotSpot ™ TM 64-Bit Server VM') с Clojure 1.8.0, и он не вызвал ошибку и не распечатал предупреждение, когда '* warn-on-reflection *' был установлен (так как должен быть только [один соответствующий конструктор] (https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#Thread -java.lang.Runnable-)). Может быть, IntelliJ связывает более старую версию Clojure? – xsc

+0

Нет, я тоже использую 1.8. Weird. – Carcigenicate

8

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

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

Отзыв принят.

4

Использование 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))) 
+0

Как отменить обратный вызов, используя этот подход? –

+0

Я обновил свой ответ, чтобы остановить «пока» только оценить условие ... в этом случае я запрашиваю значение tru/false. И в вашем repl для остановки while $> (stop-периодическая функция) – tangrammer

+1

не забудьте добавить зависимость core.async к вашему project.clj '' '[org.clojure/clojure" 1.5.1 "] [org .clojure/core.async "0.1.267.0-0d7780-alpha"] '' ' – tangrammer

6

Другой вариант должен был бы использовать java.util.Timer в schedualeAtFixedRate method

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

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

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

(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 

свиданья.

+2

Это довольно прямолинейно, но одно, о чем нужно знать: поскольку вы никогда не понимаете будущего, вы никогда не увидите никаких исключений. Либо убедитесь, что обратный вызов имеет try-catch, чтобы записывать все броски, или добавить try-catch вокруг (вызываемый) в приведенном выше коде. – Marc

6

Так я бы сделал ядро.асинхронная версия с каналом остановки.

(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) 
Смежные вопросы