【问题标题】:How do you use an agent and core.async to properly log asynchronously in Clojure?您如何使用代理和 core.async 在 Clojure 中正确异步记录?
【发布时间】:2014-07-01 04:14:27
【问题描述】:

在下面的代码中,我使用了一个 go 循环,它侦听通道并将读取的值写入代理。然后,在同一个循环中,日志到文件功能应该将代理上的每个项目写入文件。但是,文件的输出是不寻常的,作为具有 [8 6 5 13] 可能会将其内容作为 865n13 吐到文件中。如果我从记录器函数中删除日志到文件调用并单独调用它,有时文件输出很好,有时不是。有人可以向我解释发生了什么,并可能向我展示一种安全的方法来展示一致的行为吗?我知道不使用代理可能是实现一致行为的最佳方式。我只是想知道,本着学习的精神,使用代理可以实现什么,不可以实现什么。提前致谢!

(defn log-to-file [file] (for [i (deref log)] (spit file (str "\n" i) :append true)))

(defn logger [file]
  (go 
   (loop []
     (when-let [v (<! print-chan)]
       (send log conj v)
       (log-to-file file)
       (recur)))))

;; testing it out

(def log (agent []))
(>!! print-chan 5)
(logger "resources/test.txt")
(deref log)

编辑:

这是根据下面接受的答案中的 cmets 重写的代码。我完全不建议使用此代码,或者就此而言,在 go 循环中使用具有副作用的代理。

(def ch (chan))
(def a (agent []))

(defn double-async-print []
    (go-loop []
       (when-let [x (<! ch)]
         (send a conj x)
         (print (clojure.string/join "\n" @a)))
     (recur)))

(go (doseq [n (range 10)] (>! ch n)))

(double-async-print) 

    ;=>jsync 0.1.0-SNAPSHOT[stdout]:    
    00
    10
    1
    20
    1
    2
    30
    1
    2
    3
    40
    1
   ...

【问题讨论】:

    标签: logging asynchronous clojure agents core.async


    【解决方案1】:

    我怀疑有很多事情正在发生相互作用,导致您的困惑。问题本身并不是在 go-loop 中使用代理。

    send 是异步的,因此无法保证您在 log-to-file 中对 deref 的调用会在循环内调用 log-to-file 时看到更改。

    您也错误地使用了带有副作用代码的for(文件io 与spit)。看起来您正在尝试将其类似于其他语言中的命令式 for-each 循环使用。 doseq 是用于处理副作用序列的正确 clojure 构造(请参阅:Difference between doseq and for in Clojure)。

    你也没有做任何事情来删除在代理中构建的向量的内容,所以即使事情是同步运行的,你也可以在文件中添加一个输出:a、:b 和 :c喜欢:

    :a
    :a
    :b
    :a
    :b
    :c
    

    从问题中不清楚这是否是您期望的代码行为。


    关于 clojure.string/join 的旁白:

    一般来说,即使您正确使用doseq,如果您只是希望将向量或其他集合输出到换行符分隔的文件,那么使用上述吐出的效率非常低。最好使用clojure.string/join,例如

    user>  (print (clojure.string/join "\n" [:a :b :c :d]))
    :a
    :b
    :c
    :d
    

    您可以使用pprint 函数来查看实际传递给 print 的字符串在转义后的样子(pprint 比这种用法更通用,并为各种 clojure 数据提供了一个很好的可视化格式):

    user> (clojure.pprint/pprint (clojure.string/join "\n" [:a :b :c :d]))
    ":a\n:b\n:c\n:d"
    

    【讨论】:

    • 谢谢亚历克斯,你的回答真的很有帮助。我忘记了你不应该使用带有副作用的代码。我将在此之后进行代理向量更新。最后一个请求,您是否会编辑您的答案以展示 clojure.string/join 如何提高写入效率的示例?
    • 可以。顺便说一句,for 在非交互式环境中不应该产生任何副作用,因为for 会产生惰性序列。我从您的其他question 看到您正在使用 LightTable。 LT 内联评估可能是它似乎几乎可以工作的部分原因。
    • 您似乎也在尝试使用代理来强制可变状态和命令式循环回到您的代码中。这在 clojure 中是非常反模式的。除了试验之外,我认为没有充分的理由希望或需要在单个逻辑循环中混合两种不同的异步机制。
    • 感谢您的所有反馈。我只是在试验,但仍处于我不知道“现实世界”应用程序的惯用语的阶段。不过,我无法从您的示例中看出 clojure.string/join 是如何写入外部文件的。还是说这是效率低下的部分?
    • 你是对的。我没有显示写入外部文件,但 print 相当于一次写入(在它调用 append 的 java.io.PrintWriter 上,在 repl 中调用 out。)在 StringBuffer 中输出的字符串(由 clojure.string/join 使用)比遍历集合并多次调用 str 要好得多。
    猜你喜欢
    • 2018-08-27
    • 1970-01-01
    • 1970-01-01
    • 2015-07-04
    • 2016-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多