fcntl,select,asyncproc在这种情况下无济于事。
无论操作系统如何,无阻塞地读取流的可靠方法是使用Queue.get_nowait():
import sys
from subprocess import PIPE, Popen
from threading import Thread
try:
from queue import Queue, Empty
except ImportError:
from Queue import Queue, Empty # python 2.x
ON_POSIX = 'posix' in sys.builtin_module_names
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()
# ... do other things here
# read line without blocking
try: line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
print('no output yet')
else: # got line
# ... do something with line
我经常遇到类似的问题;我经常编写的Python程序需要能够执行一些主要功能,同时从命令行(stdin)接受用户输入。简单地将用户输入处理功能放在另一个线程中并不能解决问题,因为readline()阻塞并且没有超时。如果主要功能已经完成并且不再需要等待进一步的用户输入,我通常希望我的程序退出,但它不能,因为readline()仍然在等待一行的另一个线程中阻塞。我发现这个问题的解决方案是使用fcntl模块使stdin成为非阻塞文件:
import fcntl
import os
import sys
# make stdin a non-blocking file
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# user input handling thread
while mainThreadIsRunning:
try: input = sys.stdin.readline()
except: continue
handleInput(input)
在我看来,这比使用选择或信号模块解决这个问题要清晰一点,但是它再一次只适用于UNIX ...
Python 3.4为异步IO引入了新的临时API - asyncio模块。
这种方法类似于@Bryan Ward的基于twisted的答案 - 定义一个协议,一旦数据准备就调用它的方法:
#!/usr/bin/env python3
import asyncio
import os
class SubprocessProtocol(asyncio.SubprocessProtocol):
def pipe_data_received(self, fd, data):
if fd == 1: # got stdout data (bytes)
print(data)
def connection_lost(self, exc):
loop.stop() # end loop.run_forever()
if os.name == 'nt':
loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol,
"myprogram.exe", "arg1", "arg2"))
loop.run_forever()
finally:
loop.close()
请参阅文档中的“子流程”。
有一个高级接口asyncio.create_subprocess_exec()返回Process对象,允许使用StreamReader.readline()协同程序异步读取一行
(使用async/awaitPython3.5+语法):
#!/usr/bin/env python3.5
import asyncio
import locale
import sys
from asyncio.subprocess import PIPE
from contextlib import closing
async def readline_and_kill(*args):
# start child process
process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)
# read line (sequence of bytes ending with b'\n') asynchronously
async for line in process.stdout:
print("got line:", line.decode(locale.getpreferredencoding(False)))
break
process.kill()
return await process.wait() # wait for the child process to exit
if sys.platform == "win32":
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
with closing(loop):
sys.exit(loop.run_until_complete(readline_and_kill(
"myprogram.exe", "arg1", "arg2")))
readline_and_kill()执行以下任务:
启动子进程,将其stdout重定向到管道
异步读取子进程'stdout中的一行
杀死子进程
等它退出
如有必要,每个步骤都可以通过超时秒限制。
尝试使用asyncproc模块。例如:
import os
from asyncproc import Process
myProc = Process("myprogram.app")
while True:
# check to see if process has ended
poll = myProc.wait(os.WNOHANG)
if poll != None:
break
# print any new output
out = myProc.read()
if out != "":
print out
该模块负责S.Lott建议的所有线程。
您可以在Twisted中轻松完成此操作。根据您现有的代码库,这可能不是那么容易使用,但如果您正在构建一个扭曲的应用程序,那么这样的事情几乎变得微不足道。您创建一个ProcessProtocol类,并覆盖outReceived()方法。 Twisted(取决于所使用的反应器)通常只是一个很大的select()循环,其中安装了回调来处理来自不同文件描述符(通常是网络套接字)的数据。所以outReceived()方法只是安装一个回调来处理来自STDOUT的数据。演示此行为的简单示例如下:
from twisted.internet import protocol, reactor
class MyProcessProtocol(protocol.ProcessProtocol):
def outReceived(self, data):
print data
proc = MyProcessProtocol()
reactor.spawnProcess(proc, './myprogram', ['./myprogram', 'arg1', 'arg2', 'arg3'])
reactor.run()
Twisted文档有一些很好的信息。
如果你围绕Twisted构建整个应用程序,它会与本地或远程的其他进程进行异步通信,就像这样非常优雅。另一方面,如果你的程序不是建立在Twisted之上,那么这实际上并没有那么有用。希望这对其他读者有帮助,即使它不适用于您的特定应用程序。
使用选择&读(1)。
import subprocess #no new requirements
def readAllSoFar(proc, retVal=''):
while (select.select([proc.stdout],[],[],0)[0]!=[]):
retVal+=proc.stdout.read(1)
return retVal
p = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE)
while not p.poll():
print (readAllSoFar(p))
对于readline() - 如:
lines = ['']
while not p.poll():
lines = readAllSoFar(p, lines[-1]).split('\n')
for a in range(len(lines)-1):
print a
lines = readAllSoFar(p, lines[-1]).split('\n')
for a in range(len(lines)-1):
print a
一种解决方案是使另一个进程执行您对进程的读取,或者使进程的线程超时。
这是超时函数的线程版本:
http://code.activestate.com/recipes/473878/
但是,你需要阅读stdout,因为它正在进入?
另一种解决方案可能是将输出转储到文件并等待进程使用p.wait()完成。
f = open('