我想弄清楚如何最好地创建异步组件,或以组件友好的方式容纳异步代码。这是我能想到的最好的,而且......感觉不太对劲。
要点:取词,reverse
和print
,最后system
。
问题1 :我无法让println
在最后停止。我希望看到个别c-chan
的{{1}}停止,但不会。
问题2 :如何正确注入deps。进入producer
/ consumer
fns?我的意思是,它们不是组件,我认为它们不是组件,因为它们没有合理的生命周期。
问题3 :我如何惯用法处理async/pipeline
- 创建名为a>b
和b>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)))
答案 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)
使用它来处理单词序列:
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
函数进行解构并调用它。