【问题标题】:Periodically calling a function in Clojure在 Clojure 中定期调用函数
【发布时间】:2014-02-19 15:57:51
【问题描述】:

我正在寻找一种在 Clojure 中定期调用函数的非常简单的方法。

JavaScript 的setInterval 有我想要的那种API。如果我在 Clojure 中重新构想它,它看起来像这样:

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

; some time later...

(clear-interval job)

出于我的目的,我不介意这是否会创建一个新线程、在线程池中运行或其他什么。时间是否准确也不是关键。事实上,所提供的时间段(以毫秒为单位)可能只是一个呼叫完成结束与下一个呼叫开始之间的延迟。

【问题讨论】:

标签: clojure periodic-task


【解决方案1】:

最简单的方法是在单独的线程中创建一个循环。

(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 来停止它。

【讨论】:

  • 这怎么不会在第三行给你一个模棱两可的构造函数错误?在 Intellij 中,我必须使用 ^Runnable 输入提示。
  • @Carcigenicate 我刚刚在使用 Clojure 1.8.0 的 REPL (Leiningen 2.6.1 on Java 1.8.0_45 Java HotSpot(TM) 64-Bit Server VM) 中尝试了它,并且在设置 *warn-on-reflection* 时它既没有产生错误也没有打印警告(因为应该只有one matching constructor)。也许 IntelliJ 捆绑了旧的 Clojure 版本?
  • 不,我也在使用 1.8。很奇怪。
【解决方案2】:

我尝试编写此代码,界面比原始问题中指定的稍有修改。这是我想出的。

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

欢迎反馈。

【讨论】:

    【解决方案3】:

    使用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)))
    

    【讨论】:

    • 如何使用这种方法取消回调?
    • 我更新了我的答案,停止“while”只评估一个条件......在这种情况下,我要求一个 tru/false 值。并在您的 repl 中停止 while $> (stop-periodic-function)
    • 别忘了将 core.async 依赖添加到你的 project.clj [org.clojure/clojure "1.5.1"] [org.clojure/core.async "0.1.267.0-0d7780-alpha"]
    • 使用 core.async 的好处之一是,如果你使用 "go" 块 clojure.github.io/core.async/#clojure.core.async/go 它将暂停执行,而不阻塞任何线程。
    • 您的任务取消示例在这里确实不习惯。正确的是有一个通道而不是一个原子和通道被关闭的取消条件(从所述通道和使用alts! 的超时通道获取)。这将提供结束循环/期间/超时。这是我之前整理的代码示例:refheap.com/21103
    【解决方案4】:

    另一种选择是使用 java.util.Timer 的 scheduleAtFixedRate 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))
    

    【讨论】:

      【解决方案5】:

      还有很多 Clo​​jure 的调度库: (从简单到非常高级)

      直接来自at-at的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)

      【讨论】:

      • 我看到了其中两个。我的需求很简单。您能否提供在这种情况下使用它们的示例?
      • 我几天前编辑了这个问题,添加了另一个库:chime (github.com/james-henderson/chime)。我看不到我的编辑,所以它是丢失了还是卡住了?
      【解决方案6】:

      如果你想要很简单

      (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
      

      再见。

      【讨论】:

      • 这很简单,但要注意一件事:因为你永远不会取消未来,所以你永远不会看到任何异常。要么确保回调有一个 try-catch 来记录所有 throwable,要么在上面的代码中添加一个 try-catch (callable)。
      【解决方案7】:

      这就是我将如何使用停止通道制作 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)
      

      【讨论】:

      • 在 Clojure 1.10 中,这抱怨,“只能从尾部位置重现。”
      • 我试图查看它是否真的在 1.10 中被破坏,但无法复制您提到的错误,它像以前一样工作。
      猜你喜欢
      • 2020-09-21
      • 2017-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 2016-02-01
      相关资源
      最近更新 更多