如何改进此Clojure组件+异步示例?

时间:2016-11-04 23:36:02

标签: clojure components core.async

我想弄清楚如何最好地创建异步组件,或以组件友好的方式容纳异步代码。这是我能想到的最好的,而且......感觉不太对劲。

要点:取词,reverseprint,最后system

问题1 :我无法让println在最后停止。我希望看到个别c-chan的{​​{1}}停止,但不会。

问题2 :如何正确注入deps。进入producer / consumer fns?我的意思是,它们不是组件,我认为它们是组件,因为它们没有合理的生命周期。

问题3 :我如何惯用法处理async/pipeline - 创建名为a>bb>c的副作用? pipeline应该是一个组件吗?

(ns pipelines.core
  (:require [clojure.core.async :as async
             :refer [go >! <! chan pipeline-blocking close!]]
            [com.stuartsierra.component :as component]))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PIPELINES
(defn a>b [a> b>]
  (pipeline-blocking 4
                     b>
                     (map clojure.string/upper-case)
                     a>))
(defn b>c [b> c>]
  (pipeline-blocking 4
                     c>
                     (map (comp (partial apply str)
                                reverse))
                     b>))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PRODUCER / CONSUMER
(defn producer [a>]
  (doseq [word ["apple" "banana" "carrot"]]
    (go (>! a> word))))

(defn consumer [c>]
  (go (while true
        (println "Your Word Is: " (<! c>)))))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SYSTEM
(defn pipeline-system [config-options]
  (let [c-chan (reify component/Lifecycle
                 (start [this]
                   (println "starting chan: " this)
                   (chan 1))
                 (stop [this]
                   (println "stopping chan: " this)
                   (close! this)))]
    (-> (component/system-map
         :a> c-chan
         :b> c-chan
         :c> c-chan)
        (component/using {}))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; RUN IT!
(def system (atom nil))
(let [_      (reset! system (component/start (pipeline-system {})))
      _      (a>b (:a> @system) (:b> @system))
      _      (b>c (:b> @system) (:c> @system))
      _      (producer (:a> @system))
      _      (consumer (:c> @system))
      _      (component/stop @system)])

修改

我开始考虑以下问题,但我不确定它是否正常关闭......

(extend-protocol component/Lifecycle
  clojure.core.async.impl.channels.ManyToManyChannel
  (start [this]
    this)
  (stop [this]
    (close! this)))

1 个答案:

答案 0 :(得分:9)

我稍微重写了你的例子,使其 reloadable

可重新加载的管道

(ns pipeline
  (:require [clojure.core.async :as ca :refer [>! <!]]
            [clojure.string :as s]))

(defn upverse [from to]
  (ca/pipeline-blocking 4
                        to
                        (map (comp s/upper-case
                                   s/reverse))
                        from))
(defn produce [ch xs]
  (doseq [word xs]
    (ca/go (>! ch word))))

(defn consume [ch]
  (ca/go-loop []
              (when-let [word (<! ch)]
                (println "your word is:" word)
                (recur))))

(defn start-engine []
  (let [[from to] [(ca/chan) (ca/chan)]]
    (upverse to from)
    (consume from)
    {:stop (fn []
             (ca/close! to)
             (ca/close! from)
             (println "engine is stopped"))
     :process (partial produce to)}))

这样你就可以(start-engine)使用它来处理单词序列:

REPL时间

boot.user=> (require '[pipeline])

boot.user=> (def engine (pipeline/start-engine))
#'boot.user/engine

用它运行

boot.user=> ((engine :process) ["apple" "banana" "carrot"])

your word is: TORRAC
your word is: ANANAB
your word is: ELPPA

boot.user=> ((engine :process) ["do" "what" "makes" "sense"])

your word is: OD
your word is: SEKAM
your word is: ESNES
your word is: TAHW

停止

boot.user=> ((:stop engine))
engine is stopped

;; engine would not process anymore
boot.user=> ((engine :process) ["apple" "banana" "carrot"])
nil

州管理

根据您打算如何使用此管道,可能根本不需要像Component那样的状态管理框架:不需要添加任何内容&#34;以防万一&#34;,在这种情况下启动和停止管道是一个调用两个函数的问题。

但是,如果此管道在具有更多状态的较大应用程序中使用,您肯定可以从状态管理库中受益。

not a fan of Component主要是因为它需要完整的应用程序购买(这使得它成为框架),但我确实尊重使用它的其他人。

安装

我建议不要在应用程序很小的情况下使用任何特定内容:例如,您可以使用其他管道/逻辑组合此管道并从-main启动它,但如果应用程序更大并且有更多不相关的状态,您需要执行以下操作才能添加mount

(defstate engine :start (start-engine)
                 :stop ((:stop engine)))

启动管道

boot.user=> (mount/start)
{:started ["#'pipeline/engine"]}

用它运行

boot.user=> ((engine :process) ["do" "what" "makes" "sense"])

your word is: OD
your word is: SEKAM
your word is: ESNES
your word is: TAHW

停止

boot.user=> (mount/stop)
engine is stopped
{:stopped ["#'pipeline/engine"]}

以下是gist with a full example,其中包含build.boot

您只需通过boot repl

下载并播放即可

[编辑]:回答评论

如果你已经迷上了Component,这应该可以让你开始:

(defrecord WordEngine []
  component/Lifecycle

  (start [component]
    (merge component (start-engine)))

  (stop [component]
    ((:stop component))
    (assoc component :process nil :stop nil)))

这一开始,将创建一个WordEngine对象,该对象具有:process 方法

你不能像普通的Clojure函数那样调用它:即来自REPL或任何名称空间只需:require,除非你传递对整个系统的引用,不推荐。

因此,为了调用它,需要将此WordEngine插入到组件系统中,并将其注入另一个组件,然后可以对:process函数进行解构并调用它。