当前位置:

首页 > 编程开发 > Python混合怎么使用同步和异步函数

Python混合怎么使用同步和异步函数

在协程函数中调用同步函数在协程函数中直接调用同步函数会阻塞事件循环,从而影响整个程序的性能。我们先来看一个例子:以下是使用异步Web框架FastAPI写的一个例子,FastAPI是比较快,但不正确的操作将会变得很慢。importtimefromfastapiimportFastAPIapp=FastAPI()@app.get("/")asyncdefroot():time.sleep(10)return{"message":"HelloWorld"}@app.get("/health")asyncdefh

本文目录

      在协程函数中调用同步函数

      在协程函数中直接调用同步函数会阻塞事件循环,从而影响整个程序的性能。我们先来看一个例子:

      以下是使用异步 Web 框架 FastAPI 写的一个例子,FastAPI 是比较快,但不正确的操作将会变得很慢。

      import time
      
      from fastapi import FastAPI
      
      app = FastAPI()
      
      
      @app.get("/")
      async def root():
          time.sleep(10)
          return {"message": "Hello World"}
      
      
      @app.get("/health")
      async def health():
          return {"status": "ok"}

      上面我们写了两个接口,假设 root 接口函数耗时 10 秒,在这 10 秒内访问 health 接口,想一想会发生什么?

      Python混合怎么使用同步和异步函数

      访问 root 接口(左),立即访问 health 接口(右),health 接口被阻塞,直至 root 接口返回后,health 接口才成功响应。

      time.sleep 就是一个「同步」函数,它会阻塞整个事件循环。

      如何解决呢?想一想以前的处理方法,如果一个函数会阻塞主线程,那么就再开一个线程让这个阻塞函数单独运行。所以,这里也是同理,开一个线程单独去运行那些阻塞式操作,比如读取文件等。

      loop.run_in_executor 方法将同步函数转换为异步非阻塞方式进行处理。具体来说,loop.run_in_executor() 可以将同步函数创建为一个线程或进程,并在其中执行该函数,从而避免阻塞事件循环。

      官方例子:在线程或者进程池中执行代码。

      那么,我们使用 loop.run_in_executor 改写上面例子,如下:

      import asyncio
      import time
      
      from fastapi import FastAPI
      
      app = FastAPI()
      
      
      @app.get("/")
      async def root():
          loop = asyncio.get_event_loop()
      
          def do_blocking_work():
              time.sleep(10)
              print("Done blocking work!!")
      
          await loop.run_in_executor(None, do_blocking_work)
          return {"message": "Hello World"}
      
      
      @app.get("/health")
      async def health():
          return {"status": "ok"}

      效果如下:

      Python混合怎么使用同步和异步函数

      root 接口被阻塞期间,health 依然正常访问互不影响。

      注意: 这里都是为了演示,实际在使用 FastAPI 开发时,你可以直接将 async def root 更换成 def root ,也就是将其换成同步接口函数,FastAPI 内部会自动创建线程处理这个同步接口函数。总的来说,FastAPI 内部也是依靠线程去处理同步函数从而避免阻塞主线程(或主线程中的事件循环)。

      在同步函数中调用异步函数

      协程只能在「事件循环」内被执行,且同一时刻只能有一个协程被执行。

      所以,在同步函数中调用异步函数,其本质就是将协程「扔进」事件循环中,等待该协程执行完获取结果即可。

      以下这些函数,都可以实现这个效果:

      • asyncio.run

      • asyncio.run_coroutine_threadsafe

      • loop.run_until_complete

      • create_task

      接下来,我们将一一讲解这些方法并举例说明。

      asyncio.run

      这个方法使用起来最简单,先看下如何使用,然后紧跟着讲一下哪些场景不能直接使用 asyncio.run

      import asyncio
      
      async def do_work():
          return 1
      
      def main():
          result = asyncio.run(do_work())
          print(result)  # 1
      
      if __name__ == "__main__":
          main()

      直接 run 就完事了,然后接受返回值即可。

      但是需要,注意的是 asyncio.run 每次调用都会新开一个事件循环,当结束时自动关闭该事件循环。

      一个线程内只存在一个事件循环,所以如果当前线程已经有存在的事件循环了,就不应该使用 asyncio.run 了,否则就会抛出如下异常:

      RuntimeError: asyncio.run() cannot be called from a running event loop

      因此,asyncio.run 用作新开一个事件循环时使用。

      asyncio.run_coroutine_threadsafe

      文档: https://docs.python.org/zh-cn/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe

      向指定事件循环提交一个协程。(线程安全)
      返回一个 concurrent.futures.Future 以等待来自其他 OS 线程的结果。

      换句话说,就是将协程丢给其他线程中的事件循环去运行。

      值得注意的是这里的「事件循环」应该是其他线程中的事件循环,非当前线程的事件循环。

      其返回的结果是一个 future 对象,如果你需要获取协程的执行结果可以使用 future.result() 获取,关于 future 对象的更多介绍,见 https://docs.python.org/zh-cn/3/library/concurrent.futures.html#concurrent.futures.Future

      下方给了一个例子,一共有两个线程:thread_with_loop 和 another_thread,分别用于启动事件循环和调用 run_coroutine_threadsafe

      import asyncio
      import threading
      import time
      
      loop = None
      
      
      def get_loop():
          global loop
          if loop is None:
              loop = asyncio.new_event_loop()
          return loop
      
      
      def another_thread():
          async def coro_func():
              return 1
      
          loop = get_loop()
          # 将协程提交到另一个线程的事件循环中执行
          future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
          # 等待协程执行结果
          print(future.result())
          # 停止事件循环
          loop.call_soon_threadsafe(loop.stop)
      
      
      def thread_with_loop():
          loop = get_loop()
          # 启动事件循环,确保事件循环不会退出,直到 loop.stop() 被调用
          loop.run_forever()
          loop.close()
      
      
      # 启动一个线程,线程内部启动了一个事件循环
      threading.Thread(target=thread_with_loop).start()
      time.sleep(1)
      # 在主线程中启动一个协程, 并将协程提交到另一个线程的事件循环中执行
      t = threading.Thread(target=another_thread)
      t.start()
      t.join()

      loop.run_until_complete

      文档: https://docs.python.org/zh-cn/3.10/library/asyncio-eventloop.html#asyncio.loop.run_until_complete

      运行直到 future ( Future 的实例 ) 被完成。

      这个方法和 asyncio.run 类似。

      具体就是传入一个协程对象或者任务,然后可以直接拿到协程的返回值。

      run_until_complete 属于 loop 对象的方法,所以这个方法的使用前提是有一个事件循环,注意这个事件循环必须是非运行状态,如果是运行中就会抛出如下异常:

      RuntimeError: This event loop is already running

      例子:

      loop = asyncio.new_event_loop()
      loop.run_until_complete(do_async_work())

      create_task

      文档: https://docs.python.org/zh-cn/3/library/asyncio-task.html#creating-tasks

      再次准确一点:要运行一个协程函数的本质是将携带协程函数的任务提交至事件循环中,由事件循环发现、调度并执行。

      其实一共就是满足两个条件:

      • 任务;

      • 事件循环。

      我们使用 async def func 定义的函数叫做协程函数,func() 这样调用之后返回的结果是协程对象,到这一步协程函数内的代码都没有被执行,直到协程对象被包装成了任务,事件循环才会“正眼看它们”。

      所以事件循环调度运行的基本单元就是任务,那为什么我们在使用 async/await 这些语句时没有涉及到任务这个概念呢?

      这是因为 await 语法糖在内部将协程对象封装成了任务,再次强调事件循环只认识任务。

      所以,想要运行一个协程对象,其实就是将协程对象封装成一个任务,至于事件循环是如何发现、调度和执行的,这个我们不用关心。

      那将协程封装成的任务的方法有哪些呢?

      • asyncio.create_task

      • asyncio.ensure_future

      • loop.create_task

      看着有好几个的,没关系,我们只关心 loop.create_task,因为其他方法最终都是调用 loop.create_task。

      使用起来也是很简单的,将协程对象传入,返回值是一个任务对象。

      async def do_work():
          return 222
      
      task = loop.create_task(do_work())

      do_work 会被异步执行,那么 do_work 的结果怎么获取呢,task.result() 可以吗?

      分情况:

      • 如果是在一个协程函数内使用 await task.result(),这是可以的;

      • 如果是在普通函数内则不行。你不可能立即获得协程函数的返回值,因为协程函数还没有被执行呢。

      asyncio.Task 运行使用 add_done_callback 添加完成时的回调函数,所以我们可以「曲线救国」,使用回调函数将结果添加到队列、Future 等等。

      我这里给个基于 concurrent.futures.Future 获取结果的例子,如下:

      import asyncio
      from asyncio import Task
      from concurrent.futures import Future
      
      from fastapi import FastAPI
      
      app = FastAPI()
      loop = asyncio.get_event_loop()
      
      
      async def do_work1():
          return 222
      
      
      @app.get("/")
      def root():
          # 新建一个 future 对象,用于接受结果值
          future = Future()
      
          # 提交任务至事件循环
          task = loop.create_task(do_work1())
      
          # 回调函数
          def done_callback(task: Task):
              # 设置结果
              future.set_result(task.result())
      
          # 为这个任务添加回调函数
          task.add_done_callback(done_callback)
      
          # future.result 会被阻塞,直到有结果返回为止
          return future.result()  # 222
      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Python
      相关文章 更多
      python删除一个列表里的重复元素的方法与代码示例
      python删除一个列表里的重复元素的方法与代码示例

      学习如何使用Python高效删除列表中的重复元素。本文提供5种常见方法的代码示例,涵盖保持顺序与不保持顺序的场景,适合初学者和进阶开发者参考。

      Python写接口的步骤及完整代码示例
      Python写接口的步骤及完整代码示例

      详细讲解使用Python编写Web接口的核心步骤与完整代码示例,涵盖环境准备、路由定义、数据校验、服务启动及调试测试方法。

      codex怎么读取word文档及docx文件操作教程
      codex怎么读取word文档及docx文件操作教程

      介绍如何利用编程工具读取和解析Word文档(docx格式),涵盖基础文本提取代码示例、结构化数据处理逻辑,以及处理复杂排版时的技术局限与替代方案。

      怎么在Python中调用C语言代码提升性能
      怎么在Python中调用C语言代码提升性能

      详细讲解在Python中调用C语言代码提升性能的完整实践指南,包含原理分析、编译步骤、ctypes与CFFI具体实现、性能对比及常见陷阱。

      Python与C语言的性能差异及底层原理
      Python与C语言的性能差异及底层原理

      深入解析Python与C语言在执行效率上的数量级差异,从编译机制、变量内存布局、动态类型推断到GIL锁,系统阐明底层核心原理。

      Python和C语言哪个更适合编程入门
      Python和C语言哪个更适合编程入门

      客观对比Python与C语言在心智负担、底层机制与学习反馈上的核心差异,提供清晰有据的选择标准与切实的入门学习路径。

      selenium上传文件教程本地文件路径设置方法
      selenium上传文件教程本地文件路径设置方法

      深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

      针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

      langgraph python guide中文入门教程详解
      langgraph python guide中文入门教程详解

      本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

      Windows 安装 Python:从官方入口到虚拟环境的可复现实践
      Windows 安装 Python:从官方入口到虚拟环境的可复现实践

      面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。