如何在库函数中使用asyncio事件循环

时间:2017-07-10 10:53:25

标签: python python-3.x python-3.5 python-asyncio event-loop

我正在尝试使用asyncio创建一个执行某些异步操作的函数,此函数的用户不应该知道asyncio涉及到幕后。 我很难理解如何使用asyncio API完成此操作,因为大多数函数似乎在使用get_event_loop访问的某个全局循环变量下运行,并且对此调用受此循环内的全局状态的影响。

我有四个例子,其中两个(foo1和foo3)似乎是合理的用例,但它们都表现出非常奇怪的行为:

async def bar(loop):
    # Disregard how simple this is, it's just for example
    s = await asyncio.create_subprocess_exec("ls", loop=loop)


def foo1():
    # Example1: Just use get_event_loop
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait_for(bar(loop), 1000))
    # On exit this is written to stderr:
    #    Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>
    #    Traceback (most recent call last):
    #      File "/usr/lib/python3.5/asyncio/base_events.py", line 510, in __del__
    #      File "/usr/lib/python3.5/asyncio/unix_events.py", line 65, in close
    #      File "/usr/lib/python3.5/asyncio/unix_events.py", line 146, in remove_signal_handler
    #      File "/usr/lib/python3.5/signal.py", line 47, in signal
    #    TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object


def foo2():
    # Example2: Use get_event_loop and close it when done
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait_for(bar(loop), 1000))  # RuntimeError: Event loop is closed  --- if foo2() is called twice
    loop.close()


def foo3():
    # Example3: Always use new_event_loop
    loop = asyncio.new_event_loop()
    loop.run_until_complete(asyncio.wait_for(bar(loop), 1000)) #RuntimeError: Cannot add child handler, the child watcher does not have a loop attached
    loop.close()


def foo4():
    # Example4: Same as foo3 but also set_event_loop to the newly created one
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)        # Polutes global event loop, callers of foo4 does not expect this.
    loop.run_until_complete(asyncio.wait_for(bar(loop), 1000))  # OK
    loop.close()

这些功能都不起作用,我没有看到任何其他明显的方法,asyncio应该如何使用?它似乎只是在假设应用程序的入口点是您可以创建和关闭全局循环的唯一位置的情况下使用。我是否必须摆弄事件循环政策?

foo3似乎是正确的解决方案但是我得到了一个错误,即使我明确地传递了循环,因为在内部create_subprocess_exec内部,它使用当前策略来获取一个新的循环,这是一个无,这是asyncio子进程中的错误吗?

我在Ubuntu上使用Python 3.5.3。

2 个答案:

答案 0 :(得分:1)

发生

foo1 错误是因为您没有关闭事件循环,请参阅此issue

foo2 ,因为您无法重复使用已关闭的事件循环。

foo3 ,因为您没有将新的事件循环设置为全局。

foo4 几乎就是你想要的,你剩下要做的就是存储旧的事件循环并在执行bar后将其设置为全局:

import asyncio


async def bar():
    # After you set new event loop global,
    # there's no need to pass loop as param to bar or anywhere else.
    process = await asyncio.create_subprocess_exec("ls")
    await process.communicate()


def sync_exec(coro):  # foo5
    old_loop = asyncio.get_event_loop()
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        loop.run_until_complete(coro)
    finally:
        loop.close()
        asyncio.set_event_loop(old_loop)


sync_exec(asyncio.wait_for(bar(), 1000))

更重要的一点是:它不清楚你为什么要隐藏在某些同步功能背后使用asyncio,但通常这是个坏主意。关于一个全局事件循环的全部内容是允许用户在此单个事件循环中运行不同的并发作业。你试图夺走这种可能性。我想你应该重新考虑这个决定。

答案 1 :(得分:0)

升级到Python 3.6,然后foo1()将起作用,而无需显式关闭默认事件循环。

不是我希望的答案,因为我们只使用3.5 :(