使用asyncio(Python 3.4 +)异步接收长时间运行的shell命令的输出?

时间:2015-12-01 12:36:08

标签: python subprocess python-asyncio

我试图弄清楚如何以非阻塞方式简单地启动一些长时间运行的shell命令,并在完成时异步处理它们的输出,按照它们完成的顺序,即使这是他们开始的另一个订单,使用Python 3.4中提供的asyncio python库并转发。

我找不到这样做的简单例子,即使在asyncio documentation itself中也是如此,这似乎也很低级。

3 个答案:

答案 0 :(得分:10)

使用get_lines()协同程序,以异步方式获取shell命令输出并将协同程序传递给asyncio.as_completed(),以便按照它们完成的顺序获得结果:

#!/usr/bin/env python3.5
import asyncio
import sys
from asyncio.subprocess import PIPE, STDOUT

async def get_lines(shell_command):
    p = await asyncio.create_subprocess_shell(shell_command,
            stdin=PIPE, stdout=PIPE, stderr=STDOUT)
    return (await p.communicate())[0].splitlines()

async def main():
    # get commands output concurrently
    coros = [get_lines('"{e}" -c "print({i:d}); import time; time.sleep({i:d})"'
                       .format(i=i, e=sys.executable))
             for i in reversed(range(5))]
    for f in asyncio.as_completed(coros): # print in the order they finish
        print(await f)


if sys.platform.startswith('win'):
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

答案 1 :(得分:2)

create_subprocess_shell正是您要找的。它会返回Process个实例,您可以wait()开启,或communicate()

答案 2 :(得分:0)

我和你的情况完全一样。就我而言,我在多个存储库目录中运行多个git fetch命令。

在第一次试用中,代码如下所示(并且cmds['git', 'fetch']):

async def run_async(path: str, cmds: List[str]):
    process = await asyncio.create_subprocess_exec(*cmds, cwd=path)
    await process.wait()

此功能适用于一个存储库,调用方为多个存储库创建任务并运行事件loop以完成任务。

尽管程序运行并且磁盘上的结果正确,但是来自不同存储库的fetch输出却是交错的。原因是await process.wait()可以在IO阻塞(文件,网络等)时随时将控制权交还给调用方(循环调度程序)。

一个简单的更改即可解决:

async def run_async(path: str, cmds: List[str]):
    """
    Run `cmds` asynchronously in `path` directory
    """
    process = await asyncio.create_subprocess_exec(
        *cmds, stdout=asyncio.subprocess.PIPE, cwd=path)
    stdout, _ = await process.communicate()
    stdout and print(stdout.decode())

这里的理由是重定向stdout使其位于一个位置。就我而言,我只是将其打印出来。如果需要输出,可以最后将其返回。

此外,打印顺序可能与开始顺序不同,在我看来,这很好。

源代码为here on github。为了提供一些背景信息,该项目是一个命令行工具,用于管理多个git repos,该工具从任何工作目录中委派git命令执行。代码少于200行,应该很容易阅读。