如何在Python中设置Popen.stdout.readline的超时时间

4
我希望能够在子进程stdout上设置超时时间,并在超时后返回一个空字符串。
这是使用asyncio的尝试,但在asyncio.wait_for中使用file.stdout.readline()失败了。
有什么好的解决方法吗?
import threading
import select
import subprocess
import queue
import time
import asyncio

class collector():
    @staticmethod
    async def readline(file, timeout=3):
        try:
            line = await asyncio.wait_for(file.stdout.readline(), timeout)
        except asyncio.TimeoutError:
            return ""
        else:
            return line

    @staticmethod
    async def background(command):
        f = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        while True:
            line = await collector.readline(file=f, timeout=3)



asyncio.run(collector.background("tail -f /tmp/2222"))

下面是调用堆栈:

  File "/tmp/script.py", line 13, in readline
    line = await asyncio.wait_for(file.stdout.readline(), timeout)
  File "/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py", line 462, in wait_for
    fut = ensure_future(fut, loop=loop)
  File "/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py", line 679, in ensure_future
    raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required

我不明白你期望发生什么。file.stdout.readline()将返回一行——一个strbytes。你不能等待一个strbytes——你已经拥有它了。 - MisterMiyagi
请注意,“asyncio”自带其自己的子进程后端 - MisterMiyagi
@MisterMiyagi,我已经尝试使用asyncio子进程,但仍然没有成功...也许您可以给我展示一些最小的示例,告诉我如何做对? - Zohar81
1个回答

4

subprocess库只提供同步函数,这些函数无法直接被asyncio使用,手动包装它们效率低下。

asyncio已经内置了自己的子进程后端。它的进程表示类似于subprocess.Popen,但允许协作等待操作。

import asyncio.subprocess

async def background(*command):
    # create subprocess via asyncio
    proc = await asyncio.create_subprocess_exec(
        *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    while True:
        line = await readline(proc.stdout, timeout=0.01)
        print("read", len(line), "characters")

# read from an async stream vvvvvvvvvvvvvvvvvv instead of a file-like object
async def readline(stream: asyncio.StreamReader, timeout: float):
    try:
        # stream.readline is a coroutine vvvvvvvvvvvv
        return await asyncio.wait_for(stream.readline(), timeout=timeout)
    except asyncio.TimeoutError:
        return ""


asyncio.run(background("cat", "/dev/random"))

这个确实很好用,谢谢。但是,如果我在asyncio.run行之后放置了更多的代码,它将永远不会被调用...也许有一种方法可以像使用线程时那样在后台运行它? - Zohar81
这并不是asyncio的正常工作方式——它应该总是在主线程中运行。如果你只想在后台运行单个任务,使用线程——除非你做许多异步的事情,否则async没有任何好处。或者,通过asyncio.to_thread使事件循环运行你的其他代码。 - MisterMiyagi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接