Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/entity-framework/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何从“stdin”创建非阻塞连续读取?_Python_Python 3.x_Subprocess_Stdin_Nonblocking - Fatal编程技术网

Python 如何从“stdin”创建非阻塞连续读取?

Python 如何从“stdin”创建非阻塞连续读取?,python,python-3.x,subprocess,stdin,nonblocking,Python,Python 3.x,Subprocess,Stdin,Nonblocking,我有一个单独的流程,它是这样创建的: p = subprocess.Popen(args = './myapp', stdin = subprocess.PIPE, stdout = subprocess.PIPE, universal_newlines=True) 稍后,我将尝试编写到p的stdin: p.stdin.write('my message\n')

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

p = subprocess.Popen(args   = './myapp',
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)
稍后,我将尝试编写到
p
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
方法关闭了
p
stdin
,因此不再进行通信

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系统上可用,但可以提供更好的性能和错误处理:)

注意:请记住,每当进程需要输入时,它通常会通过
stdout
指示输入,因此您仍然会使用
select.epoll
注册
stdout
,以检查是否“等待输入”。您可以注册
select.EPOLLIN
来检查输入是否已给出,但我几乎看不到这一点,因为请记住,您选择输入到您应该已经知道正在“发生”的流程中的内容

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

是一个做得非常好的库,例如,它可以与
SSH
一起使用

它的工作原理与子流程稍有不同,但可能是一个很好的替代方案

让subprocess.popen使用SSH 如果这是您想要的,我将重定向到另一个问题+答案(因为SSH将以受保护的方式生成
stdin


为了调查您的程序,我编写了自己的“不断地将内容流式传输到cat并捕获它返回的内容”程序。我没有实现它的子流程部分,但希望结构类似

这一行关于你的节目很奇怪

for line in iter(sys.stdin.readline, ''):
    q.put(line)
sys.stdin.close()
看起来很像

for line in stdin:
    q.put(line)
请注意,循环将在管道关闭时结束,之后无需重新关闭

如果您需要连续异步读取stdin,您应该能够构造一个与下面代码中的
child\u reader
几乎相同的读取线程。只需将
child.stdout
替换为
stdin

import subprocess
import threading
import random

# We may need to guard this?
child = subprocess.Popen('cat', stdout=subprocess.PIPE, stdin=subprocess.PIPE)

# Continuously print what the process outputs...
def print_child():
    for line in child.stdout:
        print(line)

child_reader = threading.Thread(target = print_child)
child_reader.start()

for i in range(10000):
    chars = 'ABC\n'
    child.stdin.write(random.choice(chars).encode())

# Send EOF.
# This kills the cat.
child.stdin.close()

# I don't think order matters here?
child.wait()
child_reader.join()

我想你可能只是没有看到正在发生的事情的输出。这里有一个完整的例子,似乎对我的框起作用,除非我完全误解了你想要什么。我所做的主要更改是将
p
stdout设置为
sys.stdout
,而不是
subprocess.PIPE
。也许我误解了回答你问题的重点,这一点至关重要

以下是完整的代码和输出:

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

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')
myapp.bat的功能非常简单:

echo "In the bat cave (script)"
python myapp.py
myapp.py包含(使用
队列
而不是
队列
-当前环境Python 2):

输出:

D:\>comms_test.py

D:\>echo "In the bat cave (script)"
"In the bat cave (script)"

D:\>python myapp.py
Hi, I'm here via popen
Started the listening threadstdin listener Thread created and started

line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 2)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 3)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 4)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 5)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue


D:\>('Queue size is', 6)
('input:', 'my message\n')
('Queue size is', 5)
('input:', 'my message\n')
('Queue size is', 4)
('input:', 'my message\n')
('Queue size is', 3)
('input:', 'my message\n')
('Queue size is', 2)
('input:', 'my message\n')
('Queue size is', 1)
('input:', 'my message\n')
('Queue size is', 0)
no input
('Queue size is', 0)
no input
('Queue size is', 0)
no input

要使一切工作正常,您必须刷新主进程(
p.stdout
)和子进程(
sys.stdout
)中的输出

通信
是否同时刷新:

  • 关闭时会刷新
    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的示例
导入队列、线程、系统、时间、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

我已经写了一个程序,它可以做…基本上所有涉及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')

如果不想结束进程,则不应使用
通信
(它只发送数据,然后等待进程终止);直接写入
p.stdin
stdin.flush()
?那么使用像@InbarRose这样的模块怎么样?@InbarRose已经尝试过了..运气不好..这个实现怎么样?@InbarRose很快就会尝试,但我希望有一种更简单的解决方法..第一:谢谢你的回答。第二:你只提到了
stdout
,这对
stdin
也有效吗,因为我的问题是具体地说是这样吗?@PeterVaro因为stdin是用户控制的(也就是你输入的东西),它本质上已经是非阻塞的了。但是鉴于你的进程可能需要输入,yes-select也适用于这一点(实际上我已经讲过了,但是我用了
stdout
select.EPOLLIN
这是我的错误,
EPOLLIN
代表
stdin
EPOLLHUP
代表
stdout
。但是我已经更新了我的答案,也许可以满足你的需要。你为什么认为
p.stdout.read()
是非阻塞的吗?它在EOF.@J.F.Sebastian纠正之前不会返回。我使用了
read()
而不是
readline()
,这是我的错误。这就是为什么我还没有完全准备好从头开始编写代码:)为什么你认为
p.stdout.readline()
是非阻塞的吗?它在换行或EOF之前不会返回。另外,
p.poll()在这里是不必要的:

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
# 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')