如何使用Clojure Core Async安排相隔'n'秒的函数列表
在我的Clojure项目中,我试图列出对API的http调用,该API有一个速率限制,每分钟只允许如何使用Clojure Core Async安排相隔'n'秒的函数列表,clojure,Clojure,在我的Clojure项目中,我试图列出对API的http调用,该API有一个速率限制,每分钟只允许n调用。我希望在所有http调用完成后返回每个响应,以便进一步处理。我不熟悉Clojure的Core Async,但认为它非常适合,但因为我需要每隔几秒钟运行每个调用,所以我也尝试使用该库。在Chime的库中,它有使用Core Async的示例,但是这些示例都在每个时间间隔调用相同的函数,这在本用例中不起作用 虽然可能有一种方法可以使用chime-async更好地服务于此用例,但我在这方面的所有尝试
n
调用。我希望在所有http调用完成后返回每个响应,以便进一步处理。我不熟悉Clojure的Core Async,但认为它非常适合,但因为我需要每隔几秒钟运行每个调用,所以我也尝试使用该库。在Chime的库中,它有使用Core Async的示例,但是这些示例都在每个时间间隔调用相同的函数,这在本用例中不起作用
虽然可能有一种方法可以使用chime-async
更好地服务于此用例,但我在这方面的所有尝试都失败了,所以我尝试用core-async简单地包装chime调用,但我可能对core-async比chime更困惑
这是我的名字空间的一个例子
(ns mp.util.schedule
(:require [chime.core :as chime]
[clojure.core.async :as a]
[tick.alpha.api :as tick]))
(defn schedule-fns
"Takes a list of functions and a duration in seconds then runs each function in the list `sec` seconds apart
optionally provide an inst to start from"
[fs sec & [{:keys [inst] :or {inst (tick/now)}}]]
(let [ch (a/chan (count fs))
chime-times (map-indexed
(fn mapped-fn [i f]
(a/put! ch (chime/chime-at [(.plusSeconds inst (* i sec))]
(fn wrapped-fn [_] (f)))))
fs)]
(doseq [chi chime-times]
(a/<!! chi))))
调用(schedule fns fns 2)
时,我想让fns
中的每个函数运行n
秒,让schedule fns
返回(1 2 3)
(函数的返回值),但这不是它要做的。它在正确的时间调用每个函数(我可以从log语句中看到),但它没有返回任何内容,并且有一个错误我不理解。我得到:
(schedule-fns fns 2)
:one :at #time/instant "2021-03-05T23:31:52.565Z"
Execution error (IllegalArgumentException) at clojure.core.async.impl.protocols/eval11496$fn$G (protocols.clj:15).
No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for class: java.lang.Boolean
:two :at #time/instant "2021-03-05T23:31:54.568Z"
:three :at #time/instant "2021-03-05T23:31:56.569Z"
如果我能得到帮助,让我的代码正确地使用核心异步(有或没有蜂鸣),我将非常感激。谢谢。试试这个:
(defn sim-fn
"simple function that prints a message and value, then returns the value"
[v m]
(println m)
v)
; list of test functions
(def fns [#(sim-fn 1 :one)
#(sim-fn 2 :two)
#(sim-fn 3 :three)])
(defn schedule-fns [fns sec]
(let [program (interpose #(Thread/sleep (* sec 1000))
fns)]
(remove #(= % nil)
(for [p program]
(p)))))
然后打电话:
> (schedule-fns fns 2)
:one
:two
:three
=> (1 2 3)
我想出了一个得到我想要的东西的方法…还有一些警告
(def results (atom []))
(defn schedule-fns
"Takes a list of functions and a duration in seconds then runs each function in the list `sec` seconds apart
optionally provide an inst to start from"
[fs sec]
(let [ch (chan (count fs))]
(go-loop []
(swap! results conj (<! ch))
(recur))
(map-indexed (fn [i f]
(println :waiting (* i sec) :seconds)
(go (<! (timeout (* i sec 1000)))
(>! ch (f))))
fs)))
(def结果(atom[]))
(定义附表fns)
“获取函数列表和以秒为单位的持续时间,然后以秒为单位运行列表中的每个函数
(可选)提供从“开始”开始的指令
[财政司司长]
(让[ch(chan(count fs))]
(转到循环[]
(swap!results conj(!ch(f)))
(fs))
这段代码具有我想要的时间和行为,但我必须使用atom
来存储响应。虽然我可以添加一个观察者来确定所有结果何时出现,但我仍然觉得我不应该这样做
我想我现在会用这个,但在某个时候我会继续努力,如果有人有比这个更好的方法,我很想看看。我有几个朋友看了这个,他们每个人都给出了不同的答案。这些肯定比我以前做的要好
(defn schedule-fns [fs secs]
(let [ret (atom {})
sink (a/chan)]
(doseq [[n f] (map-indexed vector fs)]
(a/thread (a/<!! (a/timeout (* 1000 n secs)))
(let [val (f)
this-ret (swap! ret assoc n val)]
(when (= (count fs) (count this-ret))
(a/>!! sink (mapv (fn [i] (get this-ret i)) (range (count fs))))))))
(a/<!! sink)))
(定义计划fns[fs秒]
(let[ret(atom{})
(a/chan)]
(doseq[[nf](映射索引向量fs)]
(a/线程(a/!!接收器(mapv(fn[i](获取此ret i))(范围(计数fs俬俬俬俬俬)
(一)/
及
(定义附表fns
[fns sec]
(让[同时(计数fns)
输出陈冯富珍(a/chan)
timedout coll(地图索引(fn[IF]
#(do(打印“等待”)
(A/< P>)如果你的目标是围绕速率限制器工作,你可以考虑在异步信道中实现它。下面是一个示例实现——该函数采用一个通道,用令牌为基础的限幅器节流它的输入并将其管到输出通道。< /P>
(需要“[clojure.core.async:as async]”
(定义速率限制通道[输入xf速率]
(让[代币(分子率)
期间(分母率)
ans(异步/信道令牌xf)
下一个(fn[](+周期(系统/当前时间毫秒)))]
(异步/go循环[c]令牌
t(下一次)]
(如果(零摄氏度)
(做
(异步/!ans x)
(再次发生(十二月三日)
ans)
下面是一个示例用法:
(let [start (System/currentTimeMillis)
input (async/to-chan (range 10))
output (rate-limiting-ch input
;; simulate an api call with roundtrip time of ~300ms
(map #(let [wait (rand-int 300)
ans {:time (- (System/currentTimeMillis) start)
:wait wait
:input %}]
(Thread/sleep wait)
ans))
;; rate limited to 2 calls per 1000ms
2/1000)]
;; consume the output
(async/go-loop []
(when-let [x (async/<! output)]
(println x)
(recur))))
这是一个很好的尝试,但它只有在所有函数立即返回时才能正常工作。问题是它调用每个函数(包括匿名线程/睡眠函数)是同步调用的,因此如果http调用需要一段时间才能返回,则所有后续子函数都会从允许的调用时间延迟。理想情况下,即使函数1尚未完成,也可以调用函数2。我确实认为使用核心异步通道的类似方法可能会起作用,我也计划尝试一下,
(defn schedule-fns
[fns sec]
(let [concurrent (count fns)
output-chan (a/chan)
timedout-coll (map-indexed (fn [i f]
#(do (println "Waiting")
(a/<!! (a/timeout (* 1000 i sec)))
(f))) fns)]
(a/pipeline-blocking concurrent
output-chan
(map (fn [f] (f)))
(a/to-chan timedout-coll))
(a/<!! (a/into [] output-chan))))
(let [start (System/currentTimeMillis)
input (async/to-chan (range 10))
output (rate-limiting-ch input
;; simulate an api call with roundtrip time of ~300ms
(map #(let [wait (rand-int 300)
ans {:time (- (System/currentTimeMillis) start)
:wait wait
:input %}]
(Thread/sleep wait)
ans))
;; rate limited to 2 calls per 1000ms
2/1000)]
;; consume the output
(async/go-loop []
(when-let [x (async/<! output)]
(println x)
(recur))))
{:time 4, :wait 63, :input 0}
{:time 68, :wait 160, :input 1}
{:time 1003, :wait 74, :input 2}
{:time 1079, :wait 151, :input 3}
{:time 2003, :wait 165, :input 4}
{:time 2169, :wait 182, :input 5}
{:time 3003, :wait 5, :input 6}
{:time 3009, :wait 18, :input 7}
{:time 4007, :wait 138, :input 8}
{:time 4149, :wait 229, :input 9}