【问题标题】:Running asyncio.subprocess.Process from Tornado RequestHandler从 Tornado RequestHandler 运行 asyncio.subprocess.Process
【发布时间】:2017-05-03 07:19:09
【问题描述】:

我正在尝试编写一个 Tornado 网络应用程序,它以协程的形式异步运行本地命令。这是精简的示例代码:

#! /usr/bin/env python3

import shlex
import asyncio
import logging

from tornado.web import Application, url, RequestHandler
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

logging.getLogger('asyncio').setLevel(logging.DEBUG)


async def run():
    command = "python3 /path/to/my/script.py"
    logging.debug('Calling command: {}'.format(command))
    process = asyncio.create_subprocess_exec(
        *shlex.split(command),
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT
    )
    logging.debug('  - process created')

    result = await process
    stdout, stderr = result.communicate()
    output = stdout.decode()
    return output

def run_sync(self, path):
    command = "python3 /path/to/my/script.py"
    logging.debug('Calling command: {}'.format(command))
    try:
        result = subprocess.run(
            *shlex.split(command),
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            check=True
        )
    except subprocess.CalledProcessError as ex:
        raise RunnerError(ex.output)
    else:
        return result.stdout


class TestRunner(RequestHandler):

    async def get(self):
        result = await run()
        self.write(result)

url_list = [
    url(r"/test", TestRunner),
]
HTTPServer(Application(url_list, debug=True)).listen(8080)
logging.debug("Tornado server started at port {}.".format(8080))
IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop')
IOLoop.instance().start()

当直接调用/path/to/my/script.py 时,它会按预期执行。此外,当我将TestHandler.get 实现为常规的同步方法(请参阅run_sync)时,它会正确执行。但是,当运行上述应用并调用/test时,日志显示:

DEBUG:asyncio:Using selector: EpollSelector
DEBUG:asyncio:execute program 'python3' stdout=stderr=<pipe>
DEBUG:asyncio:process 'python3' created: pid 21835

但是,ps 显示进程挂起:

$ ps -ef | grep 21835
berislav 21835 21834  0 19:19 pts/2    00:00:00 [python3] <defunct>

我感觉我没有实现正确的循环,或者我做错了,但是我看到的所有examples 都展示了如何使用asyncio.get_event_loop().run_until_complete(your_coro()),我找不到太多关于结合 asyncio 和 Tornado。欢迎所有建议!

【问题讨论】:

    标签: python python-3.x tornado python-3.5 python-asyncio


    【解决方案1】:

    由于单例 SIGCHLD 处理程序,子进程很棘手。在 asyncio 中,这意味着它们仅适用于“主”事件循环。如果您将tornado.ioloop.IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop') 更改为tornado.platform.asyncio.AsyncIOMainLoop().install(),则该示例有效。还需要进行其他一些清理工作;这是完整的代码:

    #! /usr/bin/env python3
    
    import shlex
    import asyncio
    import logging
    
    import tornado.platform.asyncio
    from tornado.web import Application, url, RequestHandler
    from tornado.httpserver import HTTPServer
    from tornado.ioloop import IOLoop
    
    logging.getLogger('asyncio').setLevel(logging.DEBUG)
    
    async def run():
        command = "python3 /path/to/my/script.py"
        logging.debug('Calling command: {}'.format(command))
        process = await asyncio.create_subprocess_exec(
            *shlex.split(command),
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.STDOUT
        )
        logging.debug('  - process created')
    
        result = await process.wait()
        stdout, stderr = await process.communicate()
        output = stdout.decode()
        return output
    
    tornado.platform.asyncio.AsyncIOMainLoop().install()
    IOLoop.instance().run_sync(run)
    

    还要注意 tornado 在tornado.process.Subprocess 中有自己的子进程接口,所以如果这是您唯一需要 asyncio 的东西,请考虑使用 Tornado 版本。请注意,在同一进程中组合 Tornado 和 asyncio 的子进程接口可能会与 SIGCHLD 处理程序产生冲突,因此您应该选择其中一个,或者以不需要 SIGCHLD 处理程序的方式使用库(例如通过仅依赖 stdout/stderr 而不是进程的退出状态)。

    【讨论】:

    • 嗨,本,感谢您的回答!您的回答不是指向一个方向,而是指向两个方向,两者似乎都运行良好——一个是使用AsyncIOMainLoop(尽管最后需要使用asyncio.get_event_loop().close() 明确关闭),另一个是Tornado 的Subprocess,我不知道,需要进一步探索。后者的文档虽然不是很完整——我的理解是可以使用 STREAM 选项 aof stdin/stdout 与子进程通信,对吗?
    • 是的,使用流选项与子进程通信。
    猜你喜欢
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-21
    • 2017-12-13
    • 2023-04-01
    相关资源
    最近更新 更多