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

如何编写支持异步与同步环境的Python通用辅助函数?

问题描述

我正尝试为项目编写通用辅助函数,但部分遗留项目处于同步环境中。最初写的辅助函数如下:

def func_for_async_and_sync(session, data):
    statement = select(Obj).where(Obj.id == data['id'])
    # 其他更多代码
    if isinstance(session, AsyncSession):
        obj = await session.execute(statement)
    else:
        obj = session.execute(statement)
    # 修改obj并返回
    return obj

显然这个函数无法正常运行,因为普通函数里不能使用await关键字。我需要在普通函数中调用AsyncSession.execute并获取它的返回值——这和常见的“从同步代码调用异步函数”场景的核心差异是必须拿到返回结果。

我曾尝试改成下面的写法,但始终触发TimeoutError,Future.result()无法正确返回结果:

loop = asyncio.get_running_loop()
obj = asyncio.run_coroutine_threadsafe(
    session.get(model_class, primary_key_value), loop
).result(timeout=5)

以下是我的最小测试代码,同样会报错:

import asyncio


async def main():
    """main是异步函数,通过asyncio.run正常启动"""
    print("BEGIN main")

    loop = asyncio.get_running_loop()
    timeout = 3
    # 创建协程
    coro = asyncio.sleep(1, result=3)

    # 将协程提交到指定事件循环
    future = asyncio.run_coroutine_threadsafe(coro, loop)

    # 等待结果,设置超时时间
    assert future.result(timeout) == 3


if __name__ == "__main__":
    asyncio.run(main())
解决方案

核心问题分析

测试代码和实际代码的问题根源是:asyncio.run_coroutine_threadsafe的设计目标是向不同线程的事件循环提交协程,但你当前的代码是在同一个线程的事件循环里调用它——这会直接导致死锁:future.result()会阻塞当前线程,而事件循环被这个阻塞操作卡住,根本无法执行提交的协程,最终触发超时错误。

正确的兼容实现方式

方案1:拆分异步/同步函数+统一入口(推荐)

这种方式逻辑清晰,避免同步异步混用的坑,同时覆盖所有场景:

import asyncio
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from sqlalchemy import select, Obj

async def _async_func(session: AsyncSession, data):
    statement = select(Obj).where(Obj.id == data['id'])
    # 执行异步查询
    result = await session.execute(statement)
    obj = result.scalar_one()
    # 修改obj的逻辑
    return obj

def _sync_func(session: Session, data):
    statement = select(Obj).where(Obj.id == data['id'])
    # 执行同步查询
    result = session.execute(statement)
    obj = result.scalar_one()
    # 修改obj的逻辑
    return obj

def func_for_async_and_sync(session, data):
    if isinstance(session, AsyncSession):
        try:
            # 检查当前是否处于异步上下文
            asyncio.get_running_loop()
            # 异步环境下直接返回协程,由调用方await
            return _async_func(session, data)
        except RuntimeError:
            # 同步环境下调用异步Session,新开事件循环执行
            return asyncio.run(_async_func(session, data))
    else:
        # 同步Session直接调用同步逻辑
        return _sync_func(session, data)

方案2:异步函数内兼容同步Session(仅适用于异步环境调用)

如果通用函数只会在异步环境中被调用,仅需要兼容传入的同步Session,可以用线程池包装同步操作,避免阻塞事件循环:

import asyncio
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from sqlalchemy import select, Obj

async def func_for_async_and_sync(session, data):
    statement = select(Obj).where(Obj.id == data['id'])
    if isinstance(session, AsyncSession):
        result = await session.execute(statement)
    else:
        # 用线程池执行同步查询,避免阻塞事件循环
        result = await asyncio.to_thread(session.execute, statement)
    obj = result.scalar_one()
    # 修改obj的逻辑
    return obj

修复测试代码(仅作原理演示,不推荐业务使用)

如果一定要用run_coroutine_threadsafe,必须在不同线程中运行目标事件循环:

import asyncio
import threading

def run_loop_in_thread(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()

async def main():
    print("BEGIN main")
    # 新建一个事件循环并在单独线程启动
    new_loop = asyncio.new_event_loop()
    loop_thread = threading.Thread(target=run_loop_in_thread, args=(new_loop,), daemon=True)
    loop_thread.start()

    timeout = 3
    coro = asyncio.sleep(1, result=3)
    # 向另一个线程的事件循环提交协程
    future = asyncio.run_coroutine_threadsafe(coro, new_loop)
    assert future.result(timeout) == 3
    # 停止另一个线程的事件循环并等待线程结束
    new_loop.call_soon_threadsafe(new_loop.stop)
    loop_thread.join()

if __name__ == "__main__":
    asyncio.run(main())

这种方式复杂度高且易出问题,不建议在业务代码中使用。

内容的提问来源于stack exchange,提问作者PaleNeutron

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:24:53