You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在描述符的__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 07:10:24