如何在描述符的__get__方法中等待异步调用完成?
异步懒加载属性的同步阻塞解决方案
核心问题分析
你需要在同步的__get__方法中等待异步的load()完成,但直接调用协程会触发未await警告,在已有运行中的事件循环里调用run_until_complete会抛出循环冲突错误,同时不想改动属性调用方式或把load()改成同步方法。
可行解决方案
利用线程池在新线程中执行异步load(),避免当前事件循环的冲突,同时同步等待线程完成,确保属性返回前数据已加载完成。
修改后的完整代码
import asyncio from concurrent.futures import ThreadPoolExecutor # 全局线程池,复用线程减少开销 _load_executor = ThreadPoolExecutor(max_workers=2) class LazyLoadableProperty: def __init__(self, getter): self.__get = getter self.__loaded = False def __get__(self, obj, owner_class): if obj is None: return self if not self.__loaded: # 在新线程中运行asyncio.run,创建独立事件循环执行load def sync_run_load(): asyncio.run(obj.load()) # 同步等待线程执行完毕 _load_executor.submit(sync_run_load).result() self.__loaded = True return self.__get(obj) class LazyLoadable: # 抽象基类,确保子类实现async load方法 async def load(self): raise NotImplementedError class A(LazyLoadable): _name: str def __init__(self): self._name = "" async def load(self): # 模拟异步加载(比如HTTP请求) await asyncio.sleep(1) self._name = "Jax" @LazyLoadableProperty def name(self): return self._name async def main(): a = A() print(a.name) # 同步调用,等待1秒后输出"Jax" asyncio.run(main())
方案说明
- 线程池隔离事件循环:通过
ThreadPoolExecutor在新线程中执行asyncio.run(obj.load()),新线程会创建独立的事件循环,不会和当前运行的主循环冲突,解决了RuntimeError问题。 - 同步等待加载完成:调用
submit(...).result()会阻塞当前线程,直到load()执行完毕,确保属性返回时数据已加载完成。 - 无侵入式改动:不需要修改属性的调用方式(依然是
a.name同步访问),也不需要改动子类的异步load()方法,符合你的需求。
注意事项
- 线程池的
max_workers可以根据实际并发场景调整,避免线程过多导致资源浪费。 - 如果
load()方法涉及线程不安全的操作,需要自行添加同步锁保证线程安全。
内容的提问来源于stack exchange,提问作者Silence
相关产品推荐
相关产品推荐

