FastAPI异步方法中调用同步方法

6 阅读2分钟

在 FastAPI 的异步(async def)视图函数中直接调用同步方法(尤其是 I/O 密集型操作,如数据库查询、文件读写、网络请求等),会导致阻塞事件循环,严重降低并发性能,甚至使异步框架的优势完全丧失。

但现实开发中,我们常需调用第三方库(如 requestspymysqlcv2 等)——它们大多是同步的。此时,不能直接调用,而应通过 asyncio.to_thread()线程池 将其“异步化”。


✅ 正确做法:使用 asyncio.to_thread()(推荐,Python 3.9+)

import asyncio
from fastapi import FastAPI

app = FastAPI()

# 同步函数(模拟耗时I/O)
def sync_blocking_task(x: int) -> int:
    import time
    time.sleep(2)  # 模拟阻塞
    return x * 2

@app.get("/async-correct")
async def async_correct(x: int):
    # ✅ 正确:将同步任务交给线程池执行,不阻塞事件循环
    result = await asyncio.to_thread(sync_blocking_task, x)
    return {"result": result}

优点:代码简洁,自动使用默认线程池,避免阻塞主事件循环。


🔁 兼容 Python < 3.9:手动创建线程池

import asyncio
from concurrent.futures import ThreadPoolExecutor
from fastapi import FastAPI

app = FastAPI()

# 全局线程池(可复用)
executor = ThreadPoolExecutor(max_workers=10)

def sync_task(x: int) -> int:
    import time
    time.sleep(2)
    return x * 2

@app.get("/async-with-pool")
async def with_pool(x: int):
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(executor, sync_task, x)
    return {"result": result}

⚠️ 注意:不要在每次请求中创建新线程池,应全局复用。


❌ 错误示范:直接调用同步函数

@app.get("/async-wrong")
async def async_wrong(x: int):
    # ❌ 危险!会阻塞整个事件循环
    result = sync_blocking_task(x)  # 直接调用
    return {"result": result}

后果

  • 所有并发请求都会被串行执行;
  • 高并发下响应时间急剧上升;
  • 失去使用 FastAPI 异步架构的意义。

🧠 何时可以安全调用同步代码?

仅限以下情况:

  • 纯 CPU 计算且耗时极短(如简单数学运算、字符串处理);
  • 不涉及 I/O、sleep、锁等待等阻塞操作

例如:

@app.get("/safe-sync")
async def safe_sync(n: int):
    # ✅ 安全:纯计算,无阻塞
    total = sum(i * i for i in range(n))
    return {"total": total}

💡 原则:只要函数可能“等待”(wait),就必须异步化或放线程池


🛠 实战建议:数据库与 HTTP 请求

场景推荐方案
数据库使用异步驱动: - PostgreSQL → asyncpg + databases - MySQL → aiomysql / asyncmy - ORM → SQLModel(同步)或 Tortoise-ORM(异步)
HTTP 请求httpx.AsyncClient 替代 requests
文件读写aiofiles
第三方同步库包装进 asyncio.to_thread()

示例:用 httpx 异步请求

import httpx

@app.get("/fetch")
async def fetch_data():
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://api.example.com/data")
        return resp.json()

✅ 总结

做法是否推荐说明
await asyncio.to_thread(sync_func)✅ 强烈推荐Python 3.9+ 最佳实践
loop.run_in_executor(thread_pool, sync_func)✅ 可用兼容旧版本
async def 中直接调用 sync_func()❌ 严禁阻塞事件循环,破坏并发
纯计算型同步代码✅ 可接受无 I/O,不影响性能

记住:FastAPI 的异步能力只有在全程非阻塞时才能发挥最大价值。遇到同步库,别犹豫——扔进线程池