如何使用asyncio从子进程流式传输stdout / stderr,并在之后获取其退出代码?

时间:2014-06-26 16:43:42

标签: python asynchronous python-3.4 python-asyncio

在Windows上的Python 3.4中,我需要通过子进程流式传输写入stdout / stderr的数据,即使用Python 3.4中引入的asyncio框架接收其输出。之后我还必须确定程序的退出代码。我怎么能这样做?

3 个答案:

答案 0 :(得分:6)

到目前为止,我提出的解决方案是使用SubprocessProtocol来接收来自子进程的输出,以及用于获取进程的相关传输'退出代码。我不知道这是否是最佳的。我的方法基于answer to a similar question by J.F. Sebastian

import asyncio
import contextlib
import os
import locale


class SubprocessProtocol(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        if fd == 1:
            name = 'stdout'
        elif fd == 2:
            name = 'stderr'
        text = data.decode(locale.getpreferredencoding(False))
        print('Received from {}: {}'.format(name, text.strip()))

    def process_exited(self):
        loop.stop()


if os.name == 'nt':
    # On Windows, the ProactorEventLoop is necessary to listen on pipes
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
with contextlib.closing(loop):
    # This will only connect to the process
    transport = loop.run_until_complete(loop.subprocess_exec(
        SubprocessProtocol, 'python', '-c', 'print(\'Hello async world!\')'))[0]
    # Wait until process has finished
    loop.run_forever()
    print('Program exited with: {}'.format(transport.get_returncode()))

答案 1 :(得分:1)

由于事件循环可能会在读取之前看到并通知进程退出 对于stdout / stderr的剩余数据,我们需要检查PIPE关闭事件 除了流程退出事件。

这是对aknuds1答案的更正:

class SubprocessProtocol(asyncio.SubprocessProtocol):
    def __init__(self):
        self._exited = False
        self._closed_stdout = False
        self._closed_stderr = False

    @property
    def finished(self):
        return self._exited and self._closed_stdout and self._closed_stderr

    def signal_exit(self):
        if not self.finished:
            return
        loop.stop()        

    def pipe_data_received(self, fd, data):
        if fd == 1:
            name = 'stdout'
        elif fd == 2:
            name = 'stderr'
        text = data.decode(locale.getpreferredencoding(False))
        print('Received from {}: {}'.format(name, text.strip()))

    def pipe_connection_lost(self, fd, exc):
        if fd == 1:
            self._closed_stdout = True
        elif fd == 2:
            self._closed_stderr = True
        self.signal_exit()

    def process_exited(self):
        self._exited = True
        self.signal_exit()

答案 2 :(得分:0)

我想使用high-level api

proc = yield from asyncio.create_subprocess_exec(
    'python', '-c', 'print(\'Hello async world!\')')

stdout, stderr = yield from proc.communicate()

retcode = proc.returncode

你还可以做更多:

yield from proc.stdin.write(b'data')
yield from proc.stdin.drain()

stdout = yield from proc.stdout.read()
stderr = yield from proc.stderr.read()

retcode = yield from proc.wait()

等等。

但是,请记住,当孩子进程没有打印任何内容时,请等待,例如,stdout可以挂起你的协程。