首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何从clojure中的子进程执行非阻塞读取标准输出?

如何从clojure中的子进程执行非阻塞读取标准输出?
EN

Stack Overflow用户
提问于 2017-07-25 01:54:16
回答 2查看 1.2K关注 0票数 3

我希望生成一个长期运行的子进程,并通过标准流与此进程进行通信。

使用海螺库,我可以生成和读取进程,并从out流读取数据:

代码语言:javascript
复制
(def my-process (sh/proc "my_dumb_process"))
  ; read 10 lines from my-process's stdout. Will block until 10 lines taken
  (take 10 (line-seq (clojure.java.io/reader (:out p))))

每当我的进程打印到stdout时,只要在stdout流中有可用的数据,我就想调用一个异步回调。

我对clojure有点陌生--有什么惯用的方法可以做到吗?我看过core.async,这很不错,但我找不到流的非阻塞解决方案。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-07-25 03:12:52

为我们的目的提供一个示例shell脚本(确保它可执行),将它放在clojure项目的根目录中,以便进行简单的测试:

代码语言:javascript
复制
$ cat dumb.sh
#!/bin/bash

for i in 1 2 3 4 5
do
    echo "Loop iteration $i"
    sleep 2
done

现在,我们将定义要执行的流程,启动它,获取stdout ((.getInputStream process)),一次读取一行,然后循环直到完成。实时阅读。

代码语言:javascript
复制
(defn run-proc
  [proc-name arg-string callback]
  (let [pbuilder (ProcessBuilder. (into-array String [proc-name arg-string]))
        process (.start pbuilder)]
    (with-open [reader (clojure.java.io/reader (.getInputStream process))]
      (loop []
        (when-let [line (.readLine ^java.io.BufferedReader reader)]
          (callback line)
          (recur))))))

测试:

代码语言:javascript
复制
(run-proc "./dumb.sh" "" println)
About to start...
Loop iteration 1
Loop iteration 2
Loop iteration 3
Loop iteration 4
Loop iteration 5
=> nil

此函数将被阻塞,对callback的调用也会阻塞;如果您希望它在单独的线程中运行,可以将它包装在future中:

代码语言:javascript
复制
(future (callback line))

对于基于核心.异步的方法:

代码语言:javascript
复制
(defn run-proc-async
  [proc-name arg-string callback]
  (let [ch (async/chan 1000 (map callback))]
    (async/thread
      (let [pbuilder (ProcessBuilder. (into-array String [proc-name arg-string]))
            process (.start pbuilder)]
        (with-open [reader (clojure.java.io/reader (.getInputStream process))]
          (loop []
            (when-let [line (.readLine ^java.io.BufferedReader reader)]
              (async/>!! ch line)
              (recur))))))
    ch))

这将您的callback函数作为换能器应用到通道上,并将结果放置在该函数返回的通道上:

代码语言:javascript
复制
(run-proc-async "./dumb.sh" "" #(let [cnt (count %)]
                                  (println "Counted" cnt "characters")
                                  cnt))

#object[clojure.core.async.impl.channels.ManyToManyChannel ...]
Counted 16 characters
Counted 16 characters
Counted 16 characters
Counted 16 characters
Counted 16 characters

(async/<!! *1)
=> 16

在本例中,通道上有一个1000的缓冲区。因此,除非您开始从通道中获取信息,否则在读取1000行之后,对>!!的调用将被阻塞。您也可以在回调中使用put!,但是这里有一个内置的1024限制,无论如何您都应该处理结果。

票数 8
EN

Stack Overflow用户

发布于 2017-07-25 22:55:15

如果您不介意使用库,您可以使用lazy-genyield 从图佩洛图书馆找到一个简单的解决方案。它的工作方式类似于Python中的生成器函数

代码语言:javascript
复制
(ns tst.demo.core
  (:use demo.core tupelo.test)
  (:require
    [clojure.java.io :as io]
    [tupelo.core :as t]
    [me.raynes.conch.low-level :as cll]
  ))
(t/refer-tupelo)

(dotest
  (let [proc          (cll/proc "dumb.sh")
        >>            (pretty proc)
        out-lines     (line-seq (io/reader (grab :out proc)))
        lazy-line-seq (lazy-gen
                        (doseq [line out-lines]
                          (yield line))) ]
    (doseq [curr-line lazy-line-seq]
      (spyx curr-line))))

使用与前面相同的dumb.sh,它将产生以下输出:

代码语言:javascript
复制
{:out  #object[java.lang.UNIXProcess$ProcessPipeInputStream 0x465b16bb "java.lang.UNIXProcess$ProcessPipeInputStream@465b16bb"],
 :in   #object[java.lang.UNIXProcess$ProcessPipeOutputStream 0xfafbc63 "java.lang.UNIXProcess$ProcessPipeOutputStream@fafbc63"],
 :err  #object[java.lang.UNIXProcess$ProcessPipeInputStream 0x59bb8f80 "java.lang.UNIXProcess$ProcessPipeInputStream@59bb8f80"],
 :process  #object[java.lang.UNIXProcess 0x553c74cc "java.lang.UNIXProcess@553c74cc"]}

; one of these is printed every 2 seconds
curr-line => "Loop iteration 1"
curr-line => "Loop iteration 2"
curr-line => "Loop iteration 3"
curr-line => "Loop iteration 4"
curr-line => "Loop iteration 5"

lazy-gen中的所有内容都是使用core.async在单独的线程中运行core.asyncdoseq急切地使用流程输出,并使用yield将其放在输出延迟序列上。第二个doseq急切地在当前线程中使用lazy-gen 的结果,并在每一行可用时立即打印出来。

替代解决方案:

一个更简单的解决方案是简单地使用这样的未来:

代码语言:javascript
复制
(dotest
  (let [proc          (cll/proc "dumb.sh")
        out-lines     (line-seq (io/reader (grab :out proc))) ]
    (future
      (doseq [curr-line out-lines]
        (spyx curr-line)))))

取得了同样的结果:

代码语言:javascript
复制
curr-line => "Loop iteration 1"
curr-line => "Loop iteration 2"
curr-line => "Loop iteration 3"
curr-line => "Loop iteration 4"
curr-line => "Loop iteration 5"
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45292625

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档