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()

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

相关文章

  • 使用Python实现企业微信的自动打卡功能

    使用Python实现企业微信的自动打卡功能

    下面就通过Python程序来实现自动打卡,原理很简单,用Python设置定时任务,然后通过adb操作手机,完成打卡功能,感兴趣的朋友跟随小编一起看看吧
    2019-04-04
  • 在VSCode中配置Python开发环境的详细教程

    在VSCode中配置Python开发环境的详细教程

    Visual Studio Code(简称VSCode)以其强大的功能和灵活的扩展性,成为了许多开发者的首选,本文将详细介绍如何在VSCode中配置Python开发环境,需要的朋友可以参考下
    2025-04-04
  • python中的plt.cm.Paired用法说明

    python中的plt.cm.Paired用法说明

    这篇文章主要介绍了python中plt.cm.Paired的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-05-05
  • Pytorch 如何实现常用正则化

    Pytorch 如何实现常用正则化

    这篇文章主要介绍了Pytorch 实现常用正则化的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-05-05
  • Python的Flask框架中的Jinja2模板引擎学习教程

    Python的Flask框架中的Jinja2模板引擎学习教程

    这篇文章主要介绍了Python的Flask框架中的Jinja2模板引擎学习教程,Jinja2模板引擎的用法也是Flask的Web开发中的重要知识,需要的朋友可以参考下
    2016-06-06
  • Python图形化界面基础篇之如何使用弹出窗口和对话框

    Python图形化界面基础篇之如何使用弹出窗口和对话框

    对于Python程序员来说,处理弹出窗口似乎并不是一个常见的任务,这篇文章主要给大家介绍了关于Python图形化界面基础篇之如何使用弹出窗口和对话框的相关资料,需要的朋友可以参考下
    2024-03-03
  • PyCharm无法登陆Codeium的解决方法

    PyCharm无法登陆Codeium的解决方法

    Codeium插件可以自动建议、解释代码、生成注释和根据注释生成代码等,本文主要介绍了PyCharm无法登陆Codeium的解决方法,感兴趣的可以了解一下
    2023-11-11
  • Python Django模板之模板过滤器与自定义模板过滤器示例

    Python Django模板之模板过滤器与自定义模板过滤器示例

    这篇文章主要介绍了Python Django模板之模板过滤器与自定义模板过滤器,结合实例形式分析了Django框架模板过滤器与自定义模板过滤器相关功能、原理、使用方法及相关操作注意事项,需要的朋友可以参考下
    2019-10-10
  • 如何将一个CSV格式的文件分割成两个CSV文件

    如何将一个CSV格式的文件分割成两个CSV文件

    这篇文章主要介绍了如何将一个CSV格式的文件分割成两个CSV文件,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Python实现个人微信号自动监控告警的示例

    Python实现个人微信号自动监控告警的示例

    今天小编就为大家分享一篇Python实现个人微信号自动监控告警的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-07-07

最新评论