Python在同步方法中执行协程方法的实现

 更新时间:2025年09月12日 08:50:44   作者:无风听海  
Python同步方法无法直接执行协程,因其返回协程对象需事件循环调度,五种解决方案涵盖不同场景:asyncio.run()适用于脚本,loop.run()需显式控制,nest_asyncio解决嵌套问题但不推荐生产,线程安全方法用于框架内任务,线程池运行协程为通用推荐方案

一、问题描述:同步方法无法直接执行协程

在 Python 中,协程(async def 函数)返回的是一个 协程对象,它不会自动执行,必须被事件循环调度执行。

同步方法中既不能使用 await,也没有运行事件循环,因此直接调用协程会导致如下问题:

async def my_coroutine():
    return 42

def sync_func():
    result = my_coroutine()  # ❌ 这里只是得到 coroutine 对象
    print(result)

# 输出类似:<coroutine object my_coroutine at 0x...>

更严重的是:

如果你尝试手动运行协程,但事件循环已经在运行(如 Jupyter Notebook、某些异步框架如 FastAPI),会抛出异常:

RuntimeError: This event loop is already running

二、解决方案总览

方案编号方法适用环境是否阻塞主线程可嵌套性
1asyncio.run()脚本 / CLI 程序主函数✅ 阻塞❌ 不能嵌套
2loop.run_until_complete()事件循环未运行的场景✅ 阻塞❌ 不能嵌套
3nest_asyncioJupyter / 教学用途✅ 阻塞✅ 可以嵌套
4asyncio.run_coroutine_threadsafe()异步框架 / 多线程后台任务✅ 阻塞✅ 安全
5线程池中运行事件循环通用同步环境✅ 阻塞✅ 安全

三、详细方案分析

方案一:使用asyncio.run()

示例

import asyncio

async def async_func():
    await asyncio.sleep(1)
    return "result"

def sync_func():
    result = asyncio.run(async_func())
    print(result)

场景适用

  • 脚本、命令行程序的主入口函数
  • 保证事件循环尚未在运行

注意

  • 不能嵌套使用,否则会抛出错误(如在 Jupyter 中使用)
RuntimeError: asyncio.run() cannot be called from a running event loop

方案二:使用loop.run_until_complete()

示例

import asyncio

async def async_func():
    await asyncio.sleep(1)
    return "result"

def sync_func():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(async_func())
    print(result)

场景适用

  • 桌面程序、脚本中希望显式控制事件循环
  • 环境中事件循环尚未在运行

注意

  • 如果事件循环已在运行,会报错:
RuntimeError: This event loop is already running

方案三:使用nest_asyncio允许事件循环嵌套

示例

pip install nest_asyncio
import asyncio
import nest_asyncio

nest_asyncio.apply()

async def async_func():
    await asyncio.sleep(1)
    return "nested result"

def sync_func():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(async_func())
    print(result)

场景适用

  • Jupyter Notebook
  • 异步框架中临时执行协程(教学、调试)

注意

  • 通过 monkey-patching 修改了事件循环行为,不推荐在生产环境使用

方案四:使用asyncio.run_coroutine_threadsafe()(线程安全)

示例

import asyncio
import threading

async def async_func():
    await asyncio.sleep(1)
    return "from thread-safe"

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

def sync_func():
    loop = asyncio.new_event_loop()
    threading.Thread(target=start_loop, args=(loop,), daemon=True).start()

    future = asyncio.run_coroutine_threadsafe(async_func(), loop)
    result = future.result()  # 阻塞直到完成
    print(result)

场景适用

  • 后台线程管理事件循环
  • 异步任务调度框架中(如 aiohttp、FastAPI)提交任务到独立的 loop
  • 多线程环境下与 asyncio 集成

注意

  • 需要手动管理线程和事件循环生命周期

方案五:在线程池中运行事件循环(推荐通用方法)

示例

import asyncio
from concurrent.futures import ThreadPoolExecutor

async def async_func():
    await asyncio.sleep(1)
    return "from thread pool"

def run_coroutine_in_thread(coro):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    return loop.run_until_complete(coro)

def sync_func():
    with ThreadPoolExecutor() as executor:
        future = executor.submit(run_coroutine_in_thread, async_func())
        result = future.result()
        print(result)

sync_func()

场景适用

  • 任何同步环境中执行协程(即使已有事件循环在运行)
  • GUI 程序、Web 框架、Jupyter 等

优点

  • 安全、无嵌套冲突
  • 与主线程隔离
  • 线程池易于集成到现有项目

四、总结对比表(按推荐优先级)

方法是否可嵌套是否阻塞推荐使用场景
asyncio.run()✅ 是普通脚本、CLI 程序
loop.run_until_complete()✅ 是明确控制事件循环、服务初始化
nest_asyncio✅ 是Jupyter、教学(⚠️ 不用于生产)
run_coroutine_threadsafe()✅ 是后台线程任务调度,框架内部
线程池运行协程✅ 是✅ 通用方法,兼容所有同步环境

最佳实践建议

  • 推荐通用封装
def run_async(coro):
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        return asyncio.run(coro)
    else:
        # 如果事件循环正在运行,使用线程池运行
        from concurrent.futures import ThreadPoolExecutor
        with ThreadPoolExecutor() as pool:
            return pool.submit(lambda: asyncio.run(coro)).result()

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 对Django的restful用法详解(自带的增删改查)

    对Django的restful用法详解(自带的增删改查)

    今天小编就为大家分享一篇对Django的restful用法详解(自带的增删改查),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-08-08
  • Python跨文件调用函数的五种实用方法

    Python跨文件调用函数的五种实用方法

    在开发Python项目时,90%的开发者都会遇到这样的困境:代码越写越长,功能越来越乱,最后变成难以维护的"意大利面条式代码",本文将手把手教你通过模块化编程,让代码结构清晰、可维护性强,并深入解析5种跨文件调用函数的实用方法,需要的朋友可以参考下
    2025-05-05
  • win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeatures2d.SIFT_create() 的问题

    win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeat

    这篇文章主要介绍了win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeatures2d.SIFT_create() 的问题,需要的朋友可以参考下
    2019-10-10
  • Flask URL传参与视图映射的实现方法

    Flask URL传参与视图映射的实现方法

    这篇文章主要介绍了Flask URL传参与视图映射的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03
  • Python实现将Excel转换为高保真图片

    Python实现将Excel转换为高保真图片

    在数据处理与自动化的办公场景中,将 Excel 表格转换为高保真的图片是一项常见且极具挑战性的需求,本文将深入探讨如何利用 Spire.XLS for Python 将 Excel 工作表转换为图片格式,感兴趣的小伙伴可以了解下
    2026-02-02
  • Python类定义和类继承详解

    Python类定义和类继承详解

    这篇文章主要介绍了Python类定义和类继承详解,本文讲解了类的私有属性、类的方法、私有的类方法、类的专有方法、类的定义、类的单继承、类的多继承等内容,需要的朋友可以参考下
    2015-05-05
  • Pytorch中的Tensorboard与Transforms搭配使用

    Pytorch中的Tensorboard与Transforms搭配使用

    这篇文章主要介绍了Pytorch中的Tensorboard与Transforms搭配使用,主要是结合了前两篇文章的的一个小练习,感兴趣的小伙伴可以来练习一下,希望对你的学习有所帮助
    2021-12-12
  • 解决Pycharm 导入其他文件夹源码的2种方法

    解决Pycharm 导入其他文件夹源码的2种方法

    今天小编就为大家分享一篇解决Pycharm 导入其他文件夹源码的2种方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-02-02
  • PyCharm添加Anaconda中的虚拟环境Python解释器出现Conda executable is not found错误解决

    PyCharm添加Anaconda中的虚拟环境Python解释器出现Conda executable is not

    这篇文章主要给大家介绍了关于PyCharm添加Anaconda中的虚拟环境Python解释器出现Conda executable is not found错误的解决办法,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-02-02
  • 一文带你掌握Python中文词频统计

    一文带你掌握Python中文词频统计

    词频统计是指在文本中计算每个词出现的次数。这篇文章主要带大家了解一下Python实现中文词频统计的方法,感兴趣的小伙伴可以了解一下
    2023-02-02

最新评论