首页 > 解决方案 > 使用 Python asyncio 运行并等待来自同步函数的异步函数

问题描述

在我的代码中,我有一个带有属性的类,偶尔需要运行异步代码。有时我需要从异步函数访问属性,有时从同步访问 - 这就是为什么我不希望我的属性是异步的。此外,我的印象是异步属性通常是代码异味。如我错了请纠正我。

我从同步属性执行异步方法并阻止进一步执行直到异步方法完成时遇到问题。

这是一个示例代码:

import asyncio


async def main():
    print('entering main')
    synchronous_property()
    print('exiting main')


def synchronous_property():
    print('entering synchronous_property')
    loop = asyncio.get_event_loop()
    try:
        # this will raise an exception, so I catch it and ignore
        loop.run_until_complete(asynchronous())
    except RuntimeError:
        pass
    print('exiting synchronous_property')


async def asynchronous():
    print('entering asynchronous')
    print('exiting asynchronous')


asyncio.run(main())

它的输出:

entering main
entering synchronous_property
exiting synchronous_property
exiting main
entering asynchronous
exiting asynchronous

首先,RuntimeError捕获似乎是错误的,但如果我不这样做,我会得到RuntimeError: This event loop is already running异常。

其次,asynchronous()函数在同步完成后最后执行。我想通过异步方法对数据集进行一些处理,所以我需要等待它完成。如果我await asyncio.sleep(0)在调用之后添加synchronous_property(),它会在完成asynchronous()之前调用main(),但这对我没有帮助。我需要在完成asynchronous()之前运行synchronous_property()

我错过了什么?我正在运行 python 3.7。

标签: python-3.xasynchronouspython-asyncio

解决方案


Asyncio 在设计上确实坚持不允许嵌套循环。但是,您始终可以在不同的线程中运行另一个事件循环。这是一个变体,它使用线程池来避免每次都必须创建一个新线程:

import asyncio, concurrent.futures

async def main():
    print('entering main')
    synchronous_property()
    print('exiting main')

pool = concurrent.futures.ThreadPoolExecutor()

def synchronous_property():
    print('entering synchronous_property')
    result = pool.submit(asyncio.run, asynchronous()).result()
    print('exiting synchronous_property', result)

async def asynchronous():
    print('entering asynchronous')
    await asyncio.sleep(1)
    print('exiting asynchronous')
    return 42

asyncio.run(main())

这段代码在每个同步->异步边界上创建了一个新的事件循环,所以如果你经常这样做,不要期望高性能。可以通过使用每个线程只创建一个事件循环asyncio.new_event_loop并将其缓存在线程局部变量中来改进它。


推荐阅读