Clojure可取消计时器

时间:2017-04-28 14:13:43

标签: timer concurrency clojure channel

我遇到了一个我没有经验可以解决的问题。

我需要在Clojure中使用带状态的计时器,它可以接收消息并累积它们。

我等了5秒钟。

当我收到例如:[:left id]消息给这个计时器时,应该取消它并执行一些带有此id的动作。当我收到[:输入id]消息给这个计时器时,我应该累积它(如果收到的id少于5个)。当我收到5:输入不同ID的消息时,我应该执行一个操作并取消定时器,否则当输入的id不够时,请在5秒后执行其他操作。

我的朋友建议我使用频道,但我们没有设法通过频道实现这一点。

主要问题是计时器的内部状态应保留在某处,但是当你使用“go”时,你不能这样做,只需执行一些功能。

提前致谢。

3 个答案:

答案 0 :(得分:1)

我建议使用Java Timer和原子来累积状态。以下是您可以执行的操作的示例:

(def timer (Timer.))
(def state (atom [] ))
(def timer-delay 5000) ; millis

(def timertask
  (proxy [TimerTask] []
    (run []
      (println "timer done")
      (println "state = " @state)
      )))

(defn start-timer []
  (println "starting timer")
  (.schedule timer timertask timer-delay))

(defn add-to-state [item]
  (Thread/sleep 500)
  (println "  adding:" item)
  (swap! state conj item))

(start-timer)
(Thread/sleep 2000)
(add-to-state :first)
(add-to-state :second)
(add-to-state :third)

带输出:

starting timer
  adding: :first
  adding: :second
  adding: :third

timer done
state =  [:first :second :third]

答案 1 :(得分:1)

看起来你想要的是ScheduledExecutorService包中的java.util.concurrent之一。它们易于设置,可以停止和重置而不会有太多麻烦。与Clojure互操作也很容易。 (在我看来,它比在Java中使用它们更容易。)

以下是使用SingleThreadScheduledExecutor设置每五秒生成一次定期呼叫的示例。

(ns cncltimer.core
  (:gen-class)
  (:import (java.util.concurrent Executors ScheduledExecutorService
                                 ScheduledFuture TimeUnit)
           (jline Terminal)))

(def executor (Executors/newSingleThreadScheduledExecutor))
(def fyutchur (atom nil))
(def state (atom []))

(defn run-every-five-seconds
  "Hightlight the contents of the specified tab."
  []
  (reify Runnable
    (run [this]
      (println "Five seconds have passed."))))

(defn cancel-future-if-needed
  "Cancel a future, if it exists. Does not interrupt
   the task if it has already started."
  []
  (when (and (not (nil? @fyutchur))
             (not (.isDone @fyutchur)))
    (.cancel @fyutchur false)))

(defn re-schedule-executor
  "Cancel any existing futures if needed and reset to
  a new delay period."
  []
  (cancel-future-if-needed)
  (reset! fyutchur (.scheduleAtFixedRate executor (run-every-five-seconds)
                                         5 5 TimeUnit/SECONDS)))

(defn shutdown-executor
  "Cancel any futures from running and shutdown
   the exector that schedules things to run."
  []
  (cancel-future-if-needed)
  (.shutdownNow executor))

; If running with lein, use "lein trampoline run"
(defn -main [& args]
  (re-schedule-executor)
  (let [t (Terminal/getTerminal)]
    (loop [k (.readCharacter t System/in)]
      (if (= k 75)
        (shutdown-executor)
        (do
          (when (= k 13)
            (swap! state conj k)
            (println "@state:" @state))
          (if (and (= k 13)
                   (>= (count @state) 5))
            (do
              (println "one action")
              (shutdown-executor))
            (recur (.readCharacter t System/in))))))))

您可以使用命令行lein trampoline run使用leiningen运行此命令,并从键盘输入一些数据。如果你什么都不做,它就会继续打印“五秒钟过去了”。随时按Enter可将数据累积到状态变量中。按left arrow关闭计时器并退出。应该忽略任何其他事情。有些东西每五秒钟运行一次,直到程序累积5次“输入”事件或者用左箭头键停止。

jline内容(我使用的是0.9.94版本)仅适用于演示中的键盘处理。您的真实计划可能不需要它。同样,trampoline的使用恰好可以正确处理键盘。您的计划可能不需要它。

答案 2 :(得分:1)

这是另一个蓝图:

(defn make-timer [sec exit-fn enter5-fn]
  (let [data (atom [])
        five-entered? (fn [] (->> @data
                                  (filter #(= (first %)))
                                  (map second)
                                  distinct
                                  count
                                  (<= 5)))]
    (future
      (loop []
        (println :tick @data)
        (if-let [[_ exit-id] (some #(= (first %) :exit) @data)]
          (future (exit-fn exit-id))
          (if (five-entered?)
            (future (enter5-fn))
            (do (Thread/sleep (* 1000 sec))
                (recur))))))
    #(swap! data conj %)))

它在单独的线程中创建具有延迟的循环,并返回可用于更新定时器内部状态的功能。当达到目标时,循环就会中断。

你可以像这样使用它:

(def send! (make-timer 5
                       (partial println :exit)
                       (partial println :entered-5)))

(send! [:exit 100])
;; after 5 seconds prints ":exit 100"
;; the same works for multiple (send! [:enter id])

可能你应该尝试使用频道来实现这一点,方案几乎是相同的:make-timer会返回你可以输入值的新频道,并且每隔五秒从那里读取数据(使用{{1} }使用此频道和alt!频道)