我在将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上的类似问题来看,没有办法得到答案"如果您知道自己处于非异步函数并且事件循环已在运行,则从协程中获取。
答案 0 :(得分: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
之外,您不会获得任何异步利益。只有多个协同程序在单个事件循环内并行运行时,才能实现任何异步优势。你应该清楚地理解它。
请注意,当外部事件循环正在运行时,您将以阻塞方式调用协同程序,这意味着您可能会冻结此外部事件循环。阅读更多相关信息here。