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

求助:异步代码中调用同步库时复用现有异步回调的方案

解决方案:在WeasyPrint同步回调中复用异步aiohttp会话

核心问题分析

你需要在WeasyPrint的同步URL获取回调中,复用已配置好中间件的aiohttp异步客户端会话,避免重复编写同步HTTP请求代码,但此前两种方案因asyncio线程模型限制失败:

  • 方案一:在子线程新建事件循环调用异步代码,但aiohttp ClientSession与创建它的事件循环绑定,跨循环使用直接报错。
  • 方案二:用asyncio.to_thread运行WeasyPrint,但在回调中直接调用主线程事件循环的run_until_complete,触发“事件循环已在运行”的错误。

可行方案:使用asyncio.run_coroutine_threadsafe

asyncio.run_coroutine_threadsafe是专门用于在非事件循环线程中,向运行中的事件循环提交协程的工具。它返回concurrent.futures.Future对象,可通过result()方法阻塞等待异步任务完成,完美适配WeasyPrint的同步回调需求。

完整实现代码

import asyncio
import io
from weasyprint import HTML
import aiohttp

async def print_weasyprint(html: io.IOBase, resources: aiohttp.ClientSession):
    # 获取主线程运行的事件循环
    loop = asyncio.get_running_loop()
    # 生成WeasyPrint所需的同步URL回调
    url_fetcher = _make_url_fetcher(resources, loop)
    # 将WeasyPrint同步逻辑放入子线程执行,避免阻塞主线程事件循环
    return await asyncio.to_thread(_print_weasyprint_sync, html, url_fetcher)

def _print_weasyprint_sync(html: io.IOBase, url_fetcher):
    out = io.BytesIO()
    HTML(
        file_obj=html,
        url_fetcher=url_fetcher,
    ).write_pdf(out)
    out.seek(0)
    return out

def _make_url_fetcher(session: aiohttp.ClientSession, loop: asyncio.AbstractEventLoop):
    def fetcher(url, *args, **kwargs):
        # 提交异步请求协程到主线程事件循环
        future = asyncio.run_coroutine_threadsafe(_fetch(session, url), loop)
        # 阻塞等待异步请求结果返回
        content, content_type = future.result()
        return {
            'string': content,
            'mime_type': content_type
        }
    return fetcher

async def _fetch(session: aiohttp.ClientSession, url: str):
    async with session.get(url) as resp:
        return await resp.content.read(), resp.content_type

关键细节说明

  1. asyncio.run_coroutine_threadsafe的作用:将异步协程提交给指定的事件循环(主线程循环),返回可在子线程中阻塞等待的Future对象,解决跨线程调用异步代码的核心问题。
  2. WeasyPrint线程隔离:用asyncio.to_thread将WeasyPrint的同步逻辑放到子线程执行,避免其阻塞主线程的事件循环,符合asyncio最佳实践。
  3. 会话完全复用:_fetch函数直接使用主线程创建的aiohttp ClientSession,完全复用已有的中间件、超时等配置,无需编写重复的同步HTTP请求代码。

注意事项

  • 确保run_coroutine_threadsafe传入的事件循环是创建ClientSession的那个循环,否则仍会出现会话与循环不匹配的错误。
  • 可给future.result()设置超时时间(如future.result(timeout=10)),避免因异步请求长时间无响应导致WeasyPrint卡住。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 22:13:13