子进程在终止前获取结果 实时获取子进程的结果

问题描述

实时获取子进程的结果

我想在子进程终止之前实时获取每个结果 (sys.stdout)。 假设我们有以下 file.py。

import time,sys
sys.stdout.write('something')
while True:
    sys.stdout.write('something else')
    time.sleep(4)

好吧,我对子进程、异步和线程模块进行了一些尝试,尽管所有方法在进程完成时都会给我结果。理想情况下,我想自己终止进程并实时获取每个结果(stdout、stderr),而不是在进程完成时。

import subprocess
proc = sp.Popen([sys.executable,"/Users/../../file.py"],stdout = subprocess.PIPE,stderr= subproces.STDOUT)
proc.communicate() #This one received the result after finish

我也尝试过在不同线程中使用 proc.stdout.readline() 模块和 threading 使用 readline asyncio,但它也会等待直到进程完成。

我发现的唯一有用的是 psutil.Popen(*args,**kwargs) 与此一起使用,我可以随时终止该进程并为此获取一些统计信息。但主要问题仍然是在每次打印时实时(异步)获取 file.py 的每个 sys.stdoutprint

*python3.6的首选方案

解决方法

如评论中所述,首要的事情是确保您的 file.py 程序确实按照您认为的方式写入数据。

例如,您展示的程序在大约 40 分钟内不会写入任何内容,因为这是以 4 秒间隔发出的 14 字节打印填充 8 KB IO 缓冲区所需的时间。更令人困惑的是,如果您在 TTY 上测试它们(即只运行它们),某些程序将出现写入数据,但当您将它们作为子进程启动时则不会。这是因为在 TTY 标准输出上是行缓冲的,而在管道上它是完全缓冲的。当输出没有被刷新时,另一个程序根本无法检测到输出,因为它被困在子进程的缓冲区中,它从不费心与任何人共享。

换句话说,不要忘记冲洗

while True:
    # or just print('something else',flush=True)
    sys.stdout.write('something else')
    sys.stdout.flush()
    time.sleep(4)

除此之外,让我们研究如何读取该输出。 Asyncio 为子进程提供了一个很好的基于流的接口,它能够在任意输出到达时访问它。例如:

import asyncio

async def main():
    loop = asyncio.get_event_loop()
    proc = await asyncio.create_subprocess_exec(
        "python","file.py",stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE
    )
    # loop.create_task() rather than asyncio.create_task() because Python 3.6
    loop.create_task(display_as_arrives(proc.stdout,'stdout'))
    loop.create_task(display_as_arrives(proc.stderr,'stderr'))
    await proc.wait()

async def display_as_arrives(stream,where):
    while True:
        # 1024 chosen arbitrarily - StreamReader.read will happily return
        # shorter chunks - this allows reading in real-time.
        output = await stream.read(1024)
        if output == b'':
            break
        print('got',where,':',output)

# run_until_complete() rather than asyncio.run() because Python 3.6
asyncio.get_event_loop().run_until_complete(main())