使用Clojure的core.async消耗文件内容

Consuming file contents with Clojure's core.async

我正在尝试使用Clojure的core.async库来消耗/处理文件中的行。 当我的代码执行时,将抛出IOException: Stream closed。 下面是一个REPL会话,它再现了与我的代码相同的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(require '[clojure.core.async :as async])
(require '[clojure.java.io :as io])

; my real code is a bit more involved with calls to drop, map, filter
; following line-seq
(def lines
  (with-open [reader (io/reader"my-file.txt")]
    (line-seq reader)))

(def ch
  (let [c (async/chan)]
    (async/go
      (doseq [ln lines]
        (async/>! c ln))
      (async/close! c))
    c))

; line that causes the error
; java.io.IOException: Stream closed
(async/<!! ch)

由于这是我第一次做这样的事情(异步+文件),所以也许我对它应该如何工作有一些误解。 有人可以说明将文件行发送到通道管道的正确方法是什么吗?

谢谢!


正如@Alan指出的那样,您对lines的定义将关闭文件而不读取其所有行,因为line-seq返回的是惰性序列。 如果您扩展使用with-open宏...

1
2
3
(macroexpand-1
 '(with-open [reader (io/reader"my-file.txt")]
    (line-seq reader)))

...您得到这个:

1
2
3
4
5
6
(clojure.core/let [reader (io/reader"my-file.txt")]
  (try
    (clojure.core/with-open []
      (line-seq reader))
    (finally
      (. reader clojure.core/close))))

您可以在完成文件读取后关闭文件,而不是立即关闭文件,以解决此问题:

1
2
3
4
5
6
7
8
(def ch
  (let [c (async/chan)]
    (async/go
      (with-open [reader (io/reader"my-file.txt")]
        (doseq [ln (line-seq reader)]
          (async/>! c ln)))
      (async/close! c))
    c))


您的问题是with-open语句。 退出此作用域后,文件将立即关闭。 因此,在读取任何行之前,请打开line-seq,然后关闭文件。

使用slurp函数,对大多数文件来说效果会更好:

1
2
3
4
(require '[clojure.string :as str])

(def file-as-str   (slurp"my-file.txt"))
(def lines         (str/split-lines file-as-str))

看到:

http://clojuredocs.org/clojure.core/slurp

http://clojuredocs.org/clojure.string/split-lines