首页 文章

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

提问于
浏览
2

我一直无法将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

我理解为什么我们得到例外,但我想知道是否有好的策略来管理这个问题 . 我在这种集成中经常遇到它,但我已经遇到过几次 - 主要是在尝试编写测试用例时 . 据我所知,从文档和StackOverflow上的类似问题来看,如果您知道自己处于非异步函数并且事件循环已经在运行,则无法从协程“获得答案” .

1 回答

  • 1

    如果您知道自己处于非异步函数并且事件循环已在运行,则无法从协程“获得答案” .

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

    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 之外的任何异步利益 . 只有当多个协程在单个事件循环内并行运行时,才能实现任何异步优势 . 你应该清楚地理解它 .

    请注意,只是你're going to call coroutine in a blocking way while outer event loop is running means that you'冒着冻结这个外部事件循环的风险 . 了解更多信息here .

相关问题