我遇到了一个我没有经验可以解决的问题。
我需要在Clojure中使用带状态的计时器,它可以接收消息并累积它们。
我等了5秒钟。
当我收到例如:[:left id]消息给这个计时器时,应该取消它并执行一些带有此id的动作。当我收到[:输入id]消息给这个计时器时,我应该累积它(如果收到的id少于5个)。当我收到5:输入不同ID的消息时,我应该执行一个操作并取消定时器,否则当输入的id不够时,请在5秒后执行其他操作。
我的朋友建议我使用频道,但我们没有设法通过频道实现这一点。
主要问题是计时器的内部状态应保留在某处,但是当你使用“go”时,你不能这样做,只需执行一些功能。
提前致谢。
答案 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!
频道)