简体   繁体   English

带有 core.async 的节流功能

[英]Throttle Functions with core.async

The number of possible executions of a function should be throttled.应该限制​​函数可能执行的次数。 So after calling a function, any repeated call should be ignored within a time period.所以在调用一个函数之后,在一段时间内任何重复的调用都应该被忽略。 If there where calls in the meantime, the last one should be executed after the time period.如果在此期间有 where 调用,则应在时间段之后执行最后一个。

Here's my approach with core.async.这是我使用 core.async 的方法。 The problem here is, that additional calls are summing up in the channel c.这里的问题是,额外的调用正在通道 c 中汇总。 I'd need a channel with only one position inside, which will be overridden by put!我需要一个内部只有一个位置的通道,它将被 put 覆盖! everytime.每次。

(defn throttle [f time]
  (let [c (chan 1)]
    (go-loop []
      (apply f (<! c))
      (<! (timeout time))
      (recur))
    (fn [& args]
      (put! c (if args args [])))))

usage:用法:

(def throttled (throttle #(print %) 4000))
(doseq [x (range 10)]
   (throttled x))

; 0
;... after 4 seconds
; 9

Does anyone have an idea how to fix this?有谁知道如何解决这个问题?

Solution解决方案

(defn throttle [f time]
  (let [c (chan (sliding-buffer 1))]
    (go-loop []
      (apply f (<! c))
      (<! (timeout time))
      (recur))
    (fn [& args]
      (put! c (or args [])))))

To solve your channel question you can use a chan with a sliding buffer:要解决您的频道问题,您可以使用带有滑动缓冲区的 chan:

user> (require '[clojure.core.async :as async])
nil
user> (def c (async/chan (async/sliding-buffer 1)))
#'user/c
user> (async/>!! c 1)
true
user> (async/>!! c 2)
true
user> (async/>!! c 3)
true
user> (async/<!! c)
3

that way only the last value put into the channel will be computed at the next interval.这样只会在下一个时间间隔计算放入通道的最后一个值。

You can use a debounce function.您可以使用去抖动功能。

I'll copy it out here:我把它复制到这里:

(defn debounce [in ms]
  (let [out (chan)]
    (go-loop [last-val nil]
      (let [val (if (nil? last-val) (<! in) last-val)
            timer (timeout ms)
            [new-val ch] (alts! [in timer])]
        (condp = ch
          timer (do (>! out val) (recur nil))
          in (recur new-val))))
    out))

Here only when in has not emitted a message for ms is the last value it emitted forwarded onto the out channel.这里只有当in没有发出ms的消息时,它才会发出转发到out通道的最后一个值。 While in continues to emit without a long enough pause between emits then all-but-the-last-message are continuously discarded.in继续发出时,发出之间没有足够长的停顿,然后所有但最后一条消息都被不断丢弃。

I've tested this function.我已经测试了这个功能。 It waits 4 seconds and then prints out 9 , which is nearly what you asked for - some tweaking required!它等待 4 秒,然后打印出9 ,这几乎是您所要求的 - 需要进行一些调整!

(defn my-sender [to-chan values]
  (go-loop [[x & xs] values]
           (>! to-chan x)
           (when (seq xs) (recur xs))))

(defn my-receiver [from-chan f]
  (go-loop []
           (let [res (<! from-chan)]
             (f res)
             (recur))))

(defn setup-and-go []
  (let [in (chan)
        ch (debounce in 4000)
        sender (my-sender in (range 10))
        receiver (my-receiver ch #(log %))])) 

And this is the version of debounce that will output as required by the question, which is 0 immediately, then wait four seconds, then 9:而这就是debounce的版本,会按照问题的要求输出,立即为0,然后等待四秒,然后是9:

(defn debounce [in ms]
  (let [out (chan)]
    (go-loop [last-val nil
              first-time true]
             (let [val (if (nil? last-val) (<! in) last-val)
                   timer (timeout (if first-time 0 ms))
                   [new-val ch] (alts! [in timer])]
               (condp = ch
                 timer (do (>! out val) (recur nil false))
                 in (recur new-val false))))
    out)) 

I've used log rather than print as you did.我使用log而不是像你那样print You can't rely on ordinary println/print functions with core.async .你不能依赖core.async普通println/print函数。 See here for an explanation.请参阅此处以获取解释。

This is taken from David Nolens blog's source code :这取自 David Nolens 博客的源代码

(defn throttle*
  ([in msecs]
    (throttle* in msecs (chan)))
  ([in msecs out]
    (throttle* in msecs out (chan)))
  ([in msecs out control]
    (go
      (loop [state ::init last nil cs [in control]]
        (let [[_ _ sync] cs]
          (let [[v sc] (alts! cs)]
            (condp = sc
              in (condp = state
                   ::init (do (>! out v)
                            (>! out [::throttle v])
                            (recur ::throttling last
                              (conj cs (timeout msecs))))
                   ::throttling (do (>! out v)
                                  (recur state v cs)))
              sync (if last 
                     (do (>! out [::throttle last])
                       (recur state nil
                         (conj (pop cs) (timeout msecs))))
                     (recur ::init last (pop cs)))
              control (recur ::init nil
                        (if (= (count cs) 3)
                          (pop cs)
                          cs)))))))
    out))

(defn throttle-msg? [x]
  (and (vector? x)
       (= (first x) ::throttle)))

(defn throttle
  ([in msecs] (throttle in msecs (chan)))
  ([in msecs out]
    (->> (throttle* in msecs out)
      (filter #(and (vector? %) (= (first %) ::throttle)))
      (map second))))

You probably also want to add a dedupe transducer to the channel.您可能还想向通道添加dedupe转换器。

I needed to pass a function to capture the args because I was using it for an input event and it was passing a mutable object.我需要传递一个函数来捕获 args,因为我将它用于输入事件并且它正在传递一个可变对象。 🤷 🤷

(defn throttle-for-mutable-args [time f arg-capture-fn]
  (let [c (async/chan (async/sliding-buffer 1))]
    (async-m/go-loop []
      (f (async/<! c))
      (async/<! (async/timeout time))
      (recur))
    (fn [& args]
      (async/put! c (apply arg-capture-fn (or args []))))))

And I use like我用像

[:input
  {:onChange (util/throttle-for-mutable-args                                      
               500
               #(really-use-arg %)                                 
               #(-> % .-target .-value))}]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM