Python 为什么异步IO子流程在创建事件循环时表现不同,除非您设置了事件循环?

Python 为什么异步IO子流程在创建事件循环时表现不同,除非您设置了事件循环?,python,python-3.x,subprocess,python-3.5,python-asyncio,Python,Python 3.x,Subprocess,Python 3.5,Python Asyncio,我有一个简单的异步代码,生成sleep3,然后等待它完成: from asyncio import SelectorEventLoop, create_subprocess_exec, \ wait_for, get_event_loop, set_event_loop def run_timeout(loop, awaitable, timeout): timed_awaitable = wait_for(awaitable, timeout=timeout, loop=l

我有一个简单的异步代码,生成
sleep3
,然后等待它完成:

from asyncio import SelectorEventLoop, create_subprocess_exec, \
    wait_for, get_event_loop, set_event_loop


def run_timeout(loop, awaitable, timeout):
    timed_awaitable = wait_for(awaitable, timeout=timeout, loop=loop)
    return loop.run_until_complete(timed_awaitable)

async def foo(loop):
    process = await create_subprocess_exec('sleep', '3', loop=loop)
    await process.wait()
    print(process.returncode)
注意它是如何进行自定义
循环的。如果我使用以下命令运行它:

loop = get_event_loop()
run_timeout(loop, foo(loop), 5)
loop.close()
它按预期工作(3秒钟后
sleep 3
成功完成并打印
0
)。但是,如果我使用自己的事件循环运行它:

loop = SelectorEventLoop()
run_timeout(loop, foo(loop), 5)
loop.close()
我得到一个
TimeoutError
(来自
run\u timeout
中的
wait\u for
):


这里有什么?我是不是误解了医生?必须将所有事件循环(您使用的)设为默认循环吗?如果是这样,那么将自定义的
循环
传递到许多异步方法(例如
create\u subprocess\u exec
wait\u for
)似乎是无用的,因为您可以传递的唯一值是
get\u event\u loop()
,这是默认值。

真奇怪。我调试了这个程序,发现很难说它是不是一个bug

让我们长话短说,在执行
create_subprocess_exec
时,您不仅需要一个事件循环,还需要一个子监视程序(用于监视子进程)。但是
create\u subprocess\u exec
没有提供一种方法来设置自定义子监视程序,它只使用默认监视程序,该监视程序附加到默认事件循环,而不是当前运行的事件循环

如果使用以下代码,它将起作用:

from asyncio import SelectorEventLoop, create_subprocess_exec, \
    wait_for, get_event_loop, set_event_loop, get_child_watcher


def run_timeout(loop, awaitable, timeout):
    timed_awaitable = wait_for(awaitable, timeout=timeout)
    return loop.run_until_complete(timed_awaitable)

async def foo():
    process = await create_subprocess_exec('sleep', '3')
    await process.wait()
    print(process.returncode)

loop = SelectorEventLoop()
# core line, get default child watcher and attach it to your custom loop.
get_child_watcher().attach_loop(loop)
run_timeout(loop, foo(), 5)
loop.close()
如果您使用
set\u event\u loop
设置默认循环,它也会将默认子监视程序重新附加到新的默认循环。这就是它起作用的原因



很难说这是一个bug还是API设计的问题。
create\u subprocess\u exec
是否应该让您传递一个自定义观察程序?如果应该这样做,就会造成混乱,因为在处理子进程时,您只会接触子监视程序。

这看起来像是
asyncio.subprocess
中的错误。在Python3.6报告下运行代码。很有趣!这个例外有意义吗?或者3.6也会引发一个bug?在
run\u timeout()
之前执行
set\u event\u loop()
是否会抑制3.6的错误?我怀疑这两个异常都是同一个错误的不同表现。(3.6中的代码要小心一点,因此它会立即注意到某些地方不正确。)但还有一些地方不正确:为什么自定义循环代码指定3作为超时?难道您不希望等待3秒钟以完成
sleep 3
(因为子流程需要一些时间来设置)的超时吗?这是有道理的。我将提交一个bug:)谢谢你的洞察力。我没有太多地使用asyncio。还有,不,那只是个打字错误。对于自定义循环代码,超时也是5。很难说这是错误还是API设计问题,因为
create\u subprocess\u exec
允许传递显式
循环,它在默认循环中无条件地使用默认观察者听上去确实像是一个bug。我不完全认为,
watcher
to
loop
类似于
flask-extension
to
flask
。实际上,对于
循环
本身,它工作得非常好。OP甚至可以使用
循环。在不传递任何冗余
循环的情况下运行
直到完成(timed\u waitiable)
,例如
wait\u for(waitiable,timeout=timeout,loop=loop)
创建子流程执行('sleep','3',loop=loop)
。但我同意,如果API设计原则隐藏了很少使用的
watcher
create\u subprocess\u exec
应该自动检测watcher是否连接到运行循环。我不确定我是否完全理解flask类比,因为我没有使用flask,但是由3.6提出的建议显示了没有明确提到儿童观察者的代码。那不是一只虫子吗?我知道所有东西都可以使用默认循环,但我的理解是,asyncio支持使用非默认循环,并且应该在不知道更高级的概念(如儿童观察者)的情况下工作。如果我的理解是正确的,我认为我同意@user4815162342的观点。我正在研究3.5.2,所以这不适用,但他们对这个问题的评论指出,异步上下文中的
get\u event\u loop()
将返回异步正在运行的循环。因此,该循环应该有这个子监视程序设置。既然
create\u subprocess\u exec
是在这个上下文中运行的,那么观察器不应该对它可用吗?另外,我还没有测试过,但我相信您的代码段会失败,因为
wait\u for
不是用
loop=loop
创建的?(至少在3.5.2上对我来说是这样:
将Future附加到了另一个循环上
loop = SelectorEventLoop()
set_event_loop(loop)
run_timeout(loop, foo(loop), 3)
loop.close()
from asyncio import SelectorEventLoop, create_subprocess_exec, \
    wait_for, get_event_loop, set_event_loop, get_child_watcher


def run_timeout(loop, awaitable, timeout):
    timed_awaitable = wait_for(awaitable, timeout=timeout)
    return loop.run_until_complete(timed_awaitable)

async def foo():
    process = await create_subprocess_exec('sleep', '3')
    await process.wait()
    print(process.returncode)

loop = SelectorEventLoop()
# core line, get default child watcher and attach it to your custom loop.
get_child_watcher().attach_loop(loop)
run_timeout(loop, foo(), 5)
loop.close()