如何从`stdin`创建非阻塞连续读取?

时间:2015-05-13 14:30:55

标签: python python-3.x subprocess stdin nonblocking

我有一个单独的过程,它是这样创建的:

p = subprocess.Popen(args   = './myapp',
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

稍后,我正在写信给p的{​​{1}}:

stdin

p.stdin.write('my message\n') 进程具有以下设置:

myapp

它正试图连续阅读新行,如下:

q = queue.Queue()
def get_input():
    for line in iter(sys.stdin.readline, ''):
        q.put(line)
    sys.stdin.close()

threading.Thread(name   = 'input-getter',
                 target = get_input).start()

不幸的是,子进程从不接收任何消息。当然,当我使用时:

try:
    print('input:', q.get_nowait())
except Empty:
    print('no input')

子流程收到消息,但正如预期的那样,p.communicate('my message\n') 方法会关闭communicate的{​​{1}},因此不再进行通信。

5 个答案:

答案 0 :(得分:7)

p = subprocess.Popen(args   = './myapp',
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

while p.poll() is None:
    data = p.stdout.readline()

这将创建一个非阻塞的进程读取,直到进程退出。 但是,这里有一些注意事项需要注意。例如,如果您也管道stderr,但不读取它..那么您很可能填充一两个缓冲区,无论如何您将挂起程序。因此,在手动操作时,请务必确保清除任何缓冲区I / O.

更好的选择是尽可能使用select.epoll(),这只适用于unix系统,但会给你带来更好的性能和错误处理能力:)

epoll = select.epoll()
epoll.register(p.stdout.fileno(), select.EPOLLHUP) # Use select.EPOLLIN for stdin.

for fileno, event in epoll.poll(1):
    if fileno == p.stdout.fileno():
        # ... Do something ...

注意:请注意,只要流程需要输入,它通常会通过stdout表示,因此您仍然会在STDOUT注册select.epoll为了检查"等待输入"。你可以注册select.EPOLLIN来检查是否给出了输入,但是我几乎看不到这一点,因为请记住,那就是你选择输入到你应该已经注意到的过程中的那个"正在发生"。

检查进程是否需要输入

您可以使用select.epoll检查进程是否正在等待输入,而不会阻止应用程序执行上述示例。但还有更好的选择。

Pexpect是一个能够很好地完成这项工作的图书馆,并与SSH合作。

它与子流程略有不同,但可能是一个不错的选择。

让subprocess.popen使用SSH

我会重定向到另一个问题+答案,如果这是你之后的事情(因为SSH会以受保护的方式产生stdin

Python + SSH Password auth (no external libraries or public/private keys)?

答案 1 :(得分:2)

我想你可能只是没有看到正在发生的事情的输出。这是一个似乎在我的盒子上工作的完整示例,除非我完全误解了你想要的东西。我所做的主要更改是将function validateForm() { isValid = true; $('input[type="text"]').each(function () { if ($(this).val() === "") { $(this).addClass('redBorderClass'); isValid = false; alert('Please fill a value in for : ' + $(this).attr('name')); } else { $(this).removeClass('redBorderClass'); } }); if (isValid) { $('#contactForm').submit(); } } 的{​​{1}}设置为stdout而不是p。也许我误解了你的问题的重点,这一点至关重要......

这是完整的代码和输出:

在发送(测试)过程中(我将其命名为test_comms.py)。我目前在Windows上,请原谅sys.stdout

subprocess.PIPE

myapp.bat非常简单:

.bat

myapp.py包含(使用import time import subprocess import sys # Note I'm sending stdout to sys.stdout for observation purposes p = subprocess.Popen(args = 'myapp.bat', stdin = subprocess.PIPE, stdout = sys.stdout, universal_newlines=True) #Send 10 messages to the process's stdin, 1 second apart for i in range(10): time.sleep(1) p.stdin.write('my message\n') 而不是echo "In the bat cave (script)" python myapp.py - 当前环境Python 2):

Queue

输出:

queue

答案 2 :(得分:2)

对于一切正常工作,你必须在主进程(p.stdout)和子进程(sys.stdout)中刷新输出。

communicate同时刷新:

  • 关闭时p.stdin冲洗
  • 等待sys.stdout输出被刷新(在退出之前)

工作main.py

的示例
import subprocess,time
import sys
p = subprocess.Popen(args   = ['python3', './myapp.py'],
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

time.sleep(0.5)
p.stdin.write('my message\n')
p.stdin.flush()
#print("ici")
for i,l in  enumerate(iter(p.stdout.readline, ''),start=1):

    print("main:received:",i,repr(l))
    if i == 6:
        break
    print("mainprocess:send:other message n°{}".format(i))
    p.stdin.write("other message n°{}\n".format(i))
    p.stdin.flush()

print("main:waiting for subprocess")
p.stdin.close()    
p.wait()

myapp.py的示例     import queue,threading,sys,time,rpdb

q = queue.Queue()
def get_input():
    for line in iter(sys.stdin.readline, ''):
        q.put(line)
    sys.stdin.close()

threading.Thread(name   = 'input-getter',
                 target = get_input).start()
for i in range(6):
    try:
        l= q.get_nowait()
        print('myapp:input:', l,end="")
        sys.stdout.flush()

    except queue.Empty:
        print("myapp:no input")
        sys.stdout.flush()    
        time.sleep(1)

结果:

main:received: 1 'myapp:no input\n'
mainprocess:send:other message n°1
main:received: 2 'myapp:input: my message\n'
mainprocess:send:other message n°2
main:received: 3 'myapp:input: other message n°1\n'
mainprocess:send:other message n°3
main:received: 4 'myapp:no input\n'
mainprocess:send:other message n°4
main:received: 5 'myapp:input: other message n°2\n'
mainprocess:send:other message n°5
main:received: 6 'myapp:input: other message n°3\n'
main:waiting for subprocess

答案 3 :(得分:1)

尝试调查你的程序,我写了自己的“不断地将内容传递给cat并抓住它返回的程序”。我没有实现它的子进程方面,但希望结构类似。

这条线对你的节目非常奇怪......

Object {idfoo: Object, idbar: Object, idbash: Object, idfoo: Object, idnext: Object}

看起来非常像

for line in iter(sys.stdin.readline, ''):
    q.put(line)
sys.stdin.close()

请注意,当管道关闭时,循环将结束,之后无需重新关闭。

如果你需要不断地异步读取stdin,你应该能够在下面的代码中构造一个与for line in stdin: q.put(line) 几乎相同的阅读线程。只需将child_reader替换为child.stdout

stdin

答案 4 :(得分:1)

我编写了一个程序,基本上所有涉及IO的内容都是异步的。它读取线程上的输入,它在线程上输出,它创建一个进程,并在线程上与该进程通信。

我不确定你的程序究竟需要完成什么,但希望这段代码能够完成它。

# Asynchronous cat program!

# Asynchronously read stdin
# Pump the results into a threadsafe queue
# Asynchronously feed the contents to cat
# Then catch the output from cat and print it
# Thread all the things

import subprocess
import threading
import queue
import sys

my_queue = queue.Queue()

# Input!
def input_method():
    for line in sys.stdin: # End on EOF
        if line == 'STOP\n': # Also end on STOP
            break
        my_queue.put(line)
input_thread = threading.Thread(target=input_method)
input_thread.start()

print ('Input thread started')


# Subprocess!
cat_process = subprocess.Popen('cat', stdout=subprocess.PIPE, stdin=subprocess.PIPE)

print ('cat process started')

queue_alive = True
# Continuously dump the queue into cat
def queue_dump_method():
    while queue_alive:
        try:
            line = my_queue.get(timeout=2)
            cat_process.stdin.write(line.encode())
            cat_process.stdin.flush() # For some reason, we have to manually flush
            my_queue.task_done() # Needed?
        except queue.Empty:
            pass
queue_dump_thread = threading.Thread(target = queue_dump_method)
queue_dump_thread.start()

print ('Queue dump thread started')

# Output!
def output_method():
    for line in cat_process.stdout:
        print(line)
output_thread = threading.Thread(target=output_method)
output_thread.start()

print ('Output thread started')


# input_thread will die when we type STOP
input_thread.join()
print ('Input thread joined')

# Now we wait for the queue to finish processing
my_queue.join()
print ('Queue empty')

queue_alive = False
queue_dump_thread.join()
print ("Queue dump thread joined")

# Send EOF to cat
cat_process.stdin.close()

# This kills the cat
cat_process.wait()
print ('cat process done')

# And make sure we're done outputting
output_thread.join()
print ('Output thread joined')