简体   繁体   English

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

[英]How to use asyncio event loop in library function

I'm trying to create a function performing some asynchronous operations using asyncio, users of this function should not need to know that asyncio is involved under the hood . 我正在尝试创建一个使用asyncio执行一些异步操作的函数,该函数的用户不需要知道asyncio是在后台进行的 I'm having a very hard time understanding how this shall be done with the asyncio API as most functions seem to operate under some global loop-variable accessed with get_event_loop and calls to this are affected by the global state inside this loop. 我很难理解如何使用asyncio API来完成此操作,因为大多数函数似乎都在通过get_event_loop访问的某些全局循环变量下运行,并且对此循环的全局状态影响对其的调用。

I have four examples here where two (foo1 and foo3) seem to be reasonable use cases but they all show very strange behaviors: 我在这里有四个示例,其中两个(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()

None of these functions work and i don't see any other obvious way to do it, how is asyncio supposed to be used? 这些功能都不起作用,我看不到任何其他明显的方式来实现,应该如何使用asyncio? It's seems like it's only designed to be used under the assumption that the entry point of the application is the only place where you can create and close the global loop. 看来,它仅是在这样的假设下使用的:应用程序的入口点是唯一可以创建和关闭全局循环的地方。 Do i have to fiddle around with event loop policies? 我是否必须摆弄事件循环策略?

foo3 seems like the correct solution but i get an error even though i explicitly pass along loop, because deep down inside create_subprocess_exec it is using the current policy to get a new loop which is None, is this a bug in asyncio subprocess? foo3似乎是正确的解决方案,但是即使我显式地通过了循环,我仍然会出错,因为在create_subprocess_exec的内部,它正在使用当前策略来获取一个新的循环,即None,这是asyncio子进程中的错误吗?

I'm using Python 3.5.3 on Ubuntu. 我在Ubuntu上使用Python 3.5.3。

foo1 error happens because you didn't close event loop, see this issue . 由于未关闭事件循环而发生foo1错误,请参见此问题

foo2 because you can't reuse closed event loop. foo2,因为您无法重用封闭的事件循环。

foo3 because you didn't set new event loop as global. foo3,因为您没有将新的事件循环设置为全局。

foo4 is almost what you want, all you left to do is store old event loop and set it back as global after bar executed: 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))

One more important thing: it's not clear why you want to hide using of asyncio behind some sync functions, but usually it's bad idea. 还有一件事重要:目前尚不清楚为什么要在某些同步功能后面隐藏asyncio的用法,但这通常是个坏主意。 Whole thing about one global event loop is to allow user to run different concurrent jobs in this single event loop. 一个全局事件循环的全部目的是允许用户在此单个事件循环中运行不同的并发作业。 You're trying to take away this possibility. 您正在尝试消除这种可能性。 I think you should reconsider this decision. 我认为您应该重新考虑这个决定。

Upgrade to Python 3.6, then foo1() will work, without the need to explicitly close the default event loop. 升级到Python 3.6,然后foo1()即可工作,而无需显式关闭默认事件循环。

Not the answer i was hoping for as we only use 3.5 :( 不是我希望的答案,因为我们只使用3.5 :(

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM