Asyncio:如何从子进程读取stdout?

2 投票
2 回答
91 浏览
提问于 2025-04-11 22:43

我遇到了一个比较简单的问题——我无法和进程的标准输出(stdout)进行通信。这个进程是一个简单的计时器,所以我想要能够启动它、停止它,并获取当前的时间。

计时器的代码是:

import argparse
import time

def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('start', type=int, default=0)

    start = parser.parse_args().start
    
    while True:
        print(start)
        start += 1
        time.sleep(1)

if __name__ == "__main__":
    main()

它的管理器代码是:

import asyncio


class RobotManager:
    def __init__(self):
        self.cmd = ["python", "stopwatch.py", "10"]
        self.robot = None

    async def start(self):
        self.robot = await asyncio.create_subprocess_exec(
            *self.cmd,
            stdout=asyncio.subprocess.PIPE,
        )

    async def stop(self):
        if self.robot:
            self.robot.kill()
            stdout = await self.robot.stdout.readline()
            print(stdout)
            await self.robot.wait()
        self.robot = None


async def main():
    robot = RobotManager()
    await robot.start()
    await asyncio.sleep(3)
    await robot.stop()
    await robot.start()
    await asyncio.sleep(3)
    await robot.stop()

asyncio.run(main())

但是每次调用 stdout.readline 时,返回的都是一个空的字节串。

当我把 stdout = await self.robot.stdout.readline() 改成 stdout, _ = await self.robot.communicate() 时,结果仍然是一个空的字节串。

如果在 RobotManager.start 方法的末尾加上 await self.robot.stdout.readline(),程序就会一直卡在那里。

不过,当我去掉 stdout=asyncio.subprocess.PIPE 和所有的 readline 调用时,子进程就会像预期那样输出到终端。

我该如何正确地从子进程的标准输出中读取数据呢?

2 个回答

0

如果你不需要实时处理输出,而是可以等到程序结束后再处理,那就可以使用这个:

await proc.wait()
output = await proc.stdout.read()

在我看来,你其实只需要用proc.communicate来把输入发送给程序就可以了。

2

在这种情况下,"proc.communicate" 是不适用的,因为提问者想要中断一个正在运行的进程。Python文档中的示例代码也展示了如何直接读取管道中的标准输出,所以这样做原则上是没有问题的。

主要的问题是,计时器进程在缓存输出。你可以试着使用这个命令:["python", "-u", "stopwatch.py", "3"]。为了调试,你还可以添加一些打印信息,显示机器人什么时候开始和结束。以下代码对我有效:

class RobotManager:
    def __init__(self):
        self.cmd = ["python", "-u", "stopwatch.py", "3"]
        self.robot = None

    async def start(self):
        print("======Starting======")
        self.robot = await asyncio.create_subprocess_exec(
            *self.cmd,
            stdout=asyncio.subprocess.PIPE,
        )
        
    async def stop(self):
        if self.robot:            
            self.robot.kill()
            stdout = await self.robot.stdout.readline()
            while stdout:
                print(stdout)
                stdout = await self.robot.stdout.readline()            
            await self.robot.wait()
            print("======Terminated======")
        self.robot = None

撰写回答