20

I'm looking for a very simple way to call a function periodically in Clojure.

JavaScript's setInterval has the kind of API I'd like. If I reimagined it in Clojure, it'd look something like this:

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

; some time later...

(clear-interval job)

For my purposes I don't mind if this creates a new thread, runs in a thread pool or something else. It's not critical that the timing is exact either. In fact, the period provided (in milliseconds) can just be a delay between the end of one call completing and the commencement of the next.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • possible duplicate of [Executing code at regularly timed intervals in Clojure](http://stackoverflow.com/questions/8220347/executing-code-at-regularly-timed-intervals-in-clojure) – A. Webb Jan 28 '14 at 14:46

7 Answers7

32

If you want very simple

(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

Good-bye.

A. Webb
  • 26,227
  • 1
  • 63
  • 95
  • 6
    This is pretty straight forward, but one thing to be aware of: since you never deref the future, you will never see any exceptions. Either be sure that callback has a try-catch to log all throwable, or add a try-catch around (callable) in the code above. – Marc Aug 25 '16 at 09:01
23

There's also quite a few scheduling libraries for Clojure: (from simple to very advanced)

Straight from the examples of the github homepage of 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))

Use (every 1000 #(println "I am cool!") my-pool :fixed-delay true) if you want a delay of a second between end of task and start of next, instead of between two starts.

NielsK
  • 6,886
  • 1
  • 24
  • 46
  • I saw two of these. My need is very simple. Could you provide examples of their use for this scenario? – Drew Noakes Jan 28 '14 at 12:39
  • 2
    I've edited this question a few days ago, adding another library: chime (https://github.com/james-henderson/chime). I can't see my edits, so either it got lost or canned ? – fmjrey Feb 10 '14 at 13:43
11

This is how I would do the core.async version with stop channel.

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

And the usage

(def job (set-interval #(println "Howdy") 2000))
; Howdy
; Howdy
(close! job)
mangolas
  • 165
  • 1
  • 7
9

The simplest approach would be to just have a loop in a separate thread.

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

You can cancel execution using Thread.interrupt():

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

You could even just use future to wrap the loop and future-cancel to stop it.

xsc
  • 5,983
  • 23
  • 30
  • How does this not give you an ambiguous constructor error on the third line? In Intellij, I had to type hint it with `^Runnable`. – Carcigenicate Jul 15 '16 at 21:39
  • @Carcigenicate I just tried it in a REPL (`Leiningen 2.6.1 on Java 1.8.0_45 Java HotSpot(TM) 64-Bit Server VM`) with Clojure 1.8.0 and it did neither produce an error nor print a warning when `*warn-on-reflection*` was set (since there should only be [one matching constructor](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#Thread-java.lang.Runnable-)). Maybe IntelliJ bundles an older Clojure version? – xsc Jul 17 '16 at 10:02
  • Nope, I'm using 1.8 as well. Weird. – Carcigenicate Jul 17 '16 at 10:46
8

I took a stab at coding this up, with a slightly modified interface than specified in the original question. Here's what I came up with.

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

Feedback welcomed.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
8

Another option would be to use java.util.Timer's scheduleAtFixedRate method

edit - multiplex tasks on a single timer, and stop a single task rather than the entire timer

(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))
Scott Bale
  • 10,649
  • 5
  • 33
  • 36
Shlomi
  • 4,708
  • 1
  • 23
  • 32
4

Using 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)))
tangrammer
  • 3,041
  • 17
  • 24
  • How can I cancel the callback using this approach? – Drew Noakes Jan 28 '14 at 12:11
  • I updated my answer, to stop the "while" only evaluate a condition... in this case i ask for a tru/false value. And in your repl to stop the while $> (stop-periodic-function) – tangrammer Jan 28 '14 at 12:37
  • 1
    dont forget to add the core.async dependency to your project.clj ```[org.clojure/clojure "1.5.1"] [org.clojure/core.async "0.1.267.0-0d7780-alpha"]``` – tangrammer Jan 28 '14 at 12:38
  • 1
    One of the good parts of using core.async is that if you use "go" block http://clojure.github.io/core.async/#clojure.core.async/go it will pause execution instead, blocking no threads. – tangrammer Jan 28 '14 at 15:27
  • Your example of the task cancellation is really not idiomatic here. Correct would be to have a channel instead of an atom and the cancellation condition that channel being closed (taking from said channel and the timeout channel using `alts!`). That would offer to end the loop /during/ the timeout. Here is a code example I put together earlier: https://www.refheap.com/21103 – Leon Grapenthin Jan 29 '14 at 12:28
  • @lgrapenthin I tried to leave "a very simple way to do it" as the question's author needed, but I agree with you that it would be improved on the way you are showing, thanks for your comments! – tangrammer Jan 29 '14 at 12:40