如何使用Clojure Core Async安排相隔'n'秒的函数列表

如何使用Clojure Core Async安排相隔'n'秒的函数列表,clojure,Clojure,在我的Clojure项目中,我试图列出对API的http调用,该API有一个速率限制,每分钟只允许n调用。我希望在所有http调用完成后返回每个响应,以便进一步处理。我不熟悉Clojure的Core Async,但认为它非常适合,但因为我需要每隔几秒钟运行每个调用,所以我也尝试使用该库。在Chime的库中,它有使用Core Async的示例,但是这些示例都在每个时间间隔调用相同的函数,这在本用例中不起作用 虽然可能有一种方法可以使用chime-async更好地服务于此用例,但我在这方面的所有尝试

在我的Clojure项目中,我试图列出对API的http调用,该API有一个速率限制,每分钟只允许
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}