如果我从非异步函数调用协程并且事件循环已经 运行,是否有可能从协程获得结果?

Is it possible to get a result from a coroutine if I'm calling it from a non-asynchronous function and the event loop is already running?

我一直无法将 asyncio 集成到旧代码库中。其中大部分是可以管理的,但我 运行 遇到了非异步函数需要调用协程的问题。这似乎最容易通过 运行 loop.run_until_complete() 在相​​关协程上完成。当这种情况发生在调用堆栈的顶部时(即,当我们可以保证循环尚未 运行 时),它工作得很好——协程可以调用任何其他协程。我 运行 遇到麻烦的地方是我们无法保证循环尚未 运行 的情况。下面的(诚然有些做作的)代码说明了这一点:

import asyncio

import aioredis
from asyncio_extras import (
        contextmanager as async_contextmanager)

async def is_flag_set(redis_pool, key):
    async with acquire_redis_connection(redis_pool) as redis_connection:
        return await redis_connection.get(key)
###

async def helper_1():
    pool = await create_redis_pool()
    return await is_flag_set(pool, 'my_key')

def test_1():
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(
            helper_1())

###

def helper_2(pool=None):
    loop = asyncio.get_event_loop()
    if pool is None:
        pool = loop.run_until_complete(create_redis_pool(
            db_number=0))
    return loop.run_until_complete(is_flag_set(pool, 'my_key'))

def test_2():
    return helper_2()

###

async def helper_3(db_number):
    pool = await create_redis_pool(
            db_number=db_number)
    return helper_2()


def test_3():
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(
            helper_3(db_number=1))

###

# Not relevant to the question, just included
# for completeness:

@async_contextmanager.async_contextmanager
async def acquire_redis_connection(pool):
    connection = await pool.acquire()
    try:
        yield connection
    finally:
        pool.release(connection)

async def create_redis_pool(db_number=0):
    global redis_pool
    redis_pool = await aioredis.create_pool(
            address=('localhost', 6379),
            db=db_number,
            encoding='utf-8',
            minsize=5,
            maxsize=15)
    return redis_pool

if __name__ == '__main__':
    print(test_3())

我的问题的重点是函数 helper_2。当它在 test_2 中被调用时,循环不是 运行,所以它可以安全地发出它的协程。但是,当它在 test_3 中调用时,循环已经开始,我们得到这个异常:

  File "/usr/local/lib/python3.6/asyncio/base_events.py", line 408, in run_forever
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

我明白为什么会出现异常,但我想知道是否有管理此问题的好策略。我在这个集成中并不经常遇到它,但我遇到过几次 - 主要是在尝试编写测试用例时。据我从文档和 Whosebug 上的类似问题可以看出,如果您知道自己处于非异步函数中并且事件循环已经 [=],则无法从协程 "get an answer" 24=].

there is no way to "get an answer" from a coroutine if you know that you are in a non-asynchronous function and that the event loop is already running.

您可以 运行 在不同的事件循环中协程。可以这样做:

global_loop = asyncio.get_event_loop()
# Change current event loop:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    return loop.run_until_complete(coro())
finally:
    # return old state to not affect outer code:
    asyncio.set_event_loop(global_loop)

但是上面的代码是阻塞的:它不能 运行 与外部协程同时存在。这意味着除了在 coro 内实现之外,您不会获得任何异步收益。只有当多个协程 运行 在单个事件循环中并发时,才能实现任何异步好处。你应该明白了吧。

请注意,当外部事件循环处于 运行ning 状态时,您将以阻塞方式调用协程这一事实意味着您冒着冻结该外部事件循环的风险。阅读更多相关信息