Python怎么使用async/await_Python异步编程async/await入门

python怎么使用async/await_python异步编程async/await入门

Python使用

async/await

的核心在于定义协程(

async def

)和等待协程完成(

await

),它让程序在等待I/O操作时可以切换到其他任务,显著提升并发性能,尤其适用于网络请求、文件读写等I/O密集型场景。

在Python中,

async/await

是实现异步编程,特别是基于协程(coroutines)的并发机制的关键语法糖。简单来说,它允许你的程序在执行一个耗时但不需要CPU计算的任务(比如等待网络响应、数据库查询或文件读写)时,暂时“暂停”当前任务,让出控制权给事件循环,去执行其他准备就绪的任务。当之前暂停的任务的I/O操作完成后,事件循环会通知它继续执行。

async def

用于定义一个协程函数。当你调用这样一个函数时,它不会立即执行,而是返回一个协程对象。这个对象本身是一个“可等待”(awaitable)的实体。

await

关键字只能在

async def

定义的协程函数内部使用。它的作用是等待一个可等待对象(比如另一个协程、一个Future或一个Task)完成。当

await

遇到一个未完成的可等待对象时,它会暂停当前协程的执行,并将控制权交还给事件循环。事件循环此时可以去运行其他协程。一旦被

await

的对象完成并返回结果,当前协程会从暂停的地方继续执行。

一个最基本的例子可能长这样:

import asyncioasync def say_hello(delay, message):    print(f"[{asyncio.current_task().get_name()}] 开始等待 {delay} 秒...")    await asyncio.sleep(delay) # 这是一个异步的非阻塞等待    print(f"[{asyncio.current_task().get_name()}] {message}!")async def main():    # 创建并运行两个协程任务    task1 = asyncio.create_task(say_hello(3, "Hello from Task 1"), name="Task-1")    task2 = asyncio.create_task(say_hello(1, "Hi from Task 2"), name="Task-2")    # 等待这两个任务完成    await task1    await task2    print("所有任务都完成了。")if __name__ == "__main__":    asyncio.run(main())

在这个例子里,

say_hello

是一个协程。

main

函数创建了两个

say_hello

的实例作为任务,并使用

await

等待它们完成。因为

asyncio.sleep

是非阻塞的,所以当

Task-1

在等待3秒时,

Task-2

可以立即开始执行它的1秒等待。这样,两个任务看起来是并发执行的,而不是像同步代码那样,先等3秒再等1秒。

立即学习“Python免费学习笔记(深入)”;

Python异步编程的核心优势是什么?它与传统同步编程模式有何不同?

在我看来,Python异步编程最核心的优势在于它能够以极低的资源开销实现高并发,尤其是在I/O密集型任务中。我们都知道,CPU的速度远超硬盘读写或网络传输。在传统的同步编程模式下,当程序发起一个网络请求或文件读取时,它会傻傻地等待,直到操作完成才能继续执行。这段等待时间,CPU其实是闲置的,什么也没做。这就像你点了一份外卖,然后就坐在门口,什么也不干,直到外卖送到。

异步编程改变了这种模式。它允许程序在等待I/O操作时“暂停”当前任务,将CPU的控制权交还给一个调度器(也就是事件循环),让调度器去运行其他已经准备好的任务。当I/O操作完成后,调度器会通知之前暂停的任务继续执行。这就像你点完外卖后,可以去洗个澡、看会儿电视,甚至再点一份水果,等外卖快到了再准备开门。这样,你的时间就得到了更有效的利用。

与传统同步编程模式的主要区别

执行模型:同步: 任务按顺序执行,一个任务必须完全结束后,下一个任务才能开始。如果其中一个任务阻塞(等待I/O),整个程序都会停滞。异步: 任务可以在等待I/O时“挂起”,让出CPU,允许其他任务运行。当挂起的任务I/O就绪后,它会被事件循环重新调度执行。它实现的是“协作式多任务”,而不是操作系统层面的抢占式多任务。资源利用:同步: 为了实现并发,通常需要创建多个线程或进程。每个线程/进程都有自己的内存开销和上下文切换成本。异步: 通常在一个单线程内实现高并发。通过协程的切换,避免了线程/进程的创建和销毁开销,以及昂贵的上下文切换。这使得它在处理大量并发连接时,内存占用和CPU开销都更低。复杂度:同步: 逻辑相对直接,易于理解和调试,因为代码的执行顺序就是编写顺序。异步: 引入了事件循环、协程、

async/await

等概念,初期学习曲线可能更陡峭。调试时也需要适应其非线性的执行流程。但一旦掌握,它能解决同步编程难以高效处理的并发问题。

我个人的体会是,很多人初次接触异步编程会觉得它很“魔幻”,甚至有点反直觉。但一旦你理解了它不是为了让CPU计算得更快,而是为了让CPU在等待外部资源时也能保持“忙碌”,去做一些有意义的事情,那么它的价值就显而易见了。它不是万能药,计算密集型任务依然需要多进程来利用多核CPU,但对于网络服务、爬虫、数据处理管道等I/O密集型应用,异步编程无疑是提升性能和响应速度的利器。

在Python中,如何启动和有效地管理

asyncio

事件循环?

启动和管理

asyncio

事件循环,是掌握异步编程的基石。事件循环是

asyncio

应用程序的“心脏”,它负责调度和执行协程。

1. 使用

asyncio.run()

(Python 3.7+ 推荐方式)

这是目前最简洁、最推荐的启动事件循环的方式。

asyncio.run()

函数是一个高级API,它为你处理了事件循环的创建、运行和关闭等所有细节。你只需要传入你的主协程即可。

import asyncioasync def main_coroutine():    print("主协程开始")    await asyncio.sleep(1)    print("主协程结束")if __name__ == "__main__":    print("启动事件循环...")    asyncio.run(main_coroutine())    print("事件循环已关闭。")
asyncio.run()

会:

创建一个新的事件循环。运行传入的协程直到完成。关闭事件循环。处理一些清理工作,比如取消所有未完成的任务。

对于大多数简单的脚本或应用程序,

asyncio.run()

是最佳选择,它避免了手动管理事件循环可能带来的复杂性和错误。

2. 手动管理事件循环 (适用于特定场景或旧代码)

在Python 3.7之前,或者当你需要在更复杂的环境中(例如,在现有线程中集成

asyncio

,或者需要自定义事件循环的行为)时,你可能需要手动管理事件循环。

import asyncioasync def my_task(name):    print(f"任务 {name} 开始")    await asyncio.sleep(2)    print(f"任务 {name} 结束")def manual_loop_management():    loop = asyncio.get_event_loop() # 获取当前线程的事件循环,如果没有则创建    try:        print("手动管理:运行任务 A")        loop.run_until_complete(my_task("A")) # 运行一个协程直到完成        print("手动管理:运行多个任务")        task_b = loop.create_task(my_task("B")) # 创建一个任务        task_c = loop.create_task(my_task("C"))        loop.run_until_complete(asyncio.gather(task_b, task_c)) # 等待多个任务完成    finally:        loop.close() # 确保事件循环被关闭if __name__ == "__main__":    # asyncio.run(main_coroutine()) # 使用推荐方式    manual_loop_management() # 使用手动方式

手动管理涉及以下步骤:

asyncio.get_event_loop()

:获取当前线程的事件循环。如果当前线程没有事件循环,它会创建一个新的。

loop.run_until_complete(coroutine)

:运行一个协程直到它完成。

loop.close()

:关闭事件循环。这会清除所有待处理的回调和任务,并释放资源。非常重要,否则可能导致资源泄露。

3. 有效地管理并发任务:

asyncio.create_task()

asyncio.gather()

仅仅启动事件循环是不够的,你还需要有效地管理多个并发执行的协程。

asyncio.create_task(coroutine)

当你想要一个协程在后台“独立”运行,而不是被

await

立即阻塞时,你需要将它包装成一个

Task

create_task

会安排协程在事件循环中运行,并立即返回一个Task对象。你可以稍后

await

这个Task对象来获取其结果或等待其完成。

async def fetch_data(url):    print(f"正在从 {url} 获取数据...")    await asyncio.sleep(2) # 模拟网络延迟    print(f"从 {url} 获取数据完成。")    return f"Data from {url}"async def main_tasks():    task1 = asyncio.create_task(fetch_data("http://example.com/api/1"))    task2 = asyncio.create_task(fetch_data("http://example.com/api/2"))    # 此时 task1 和 task2 已经开始在后台运行了    print("任务已创建,正在等待结果...")    result1 = await task1 # 等待 task1 完成    result2 = await task2 # 等待 task2 完成    print(f"收到结果: {result1}, {result2}")# asyncio.run(main_tasks())

*`asyncio.gather(coros_or_futures, loop=None, return_exceptions=False)`:** 这是一个非常实用的工具,用于同时运行并等待多个可等待对象(协程或Future)完成。它会收集所有结果,并按传入顺序返回。

async def main_gather():    results = await asyncio.gather(        fetch_data("http://example.com/api/3"),        fetch_data("http://example.com/api/4"),        fetch_data("http://example.com/api/5")    )    print(f"所有数据都已获取: {results}")# asyncio.run(main_gather())
asyncio.gather()

在等待多个任务时非常方便,它能确保所有任务都被调度并等待其完成。

我的经验告诉我,事件循环是

asyncio

的基石,理解它的启动、运行和关闭机制,以及如何使用

create_task

gather

来编排协程,是编写高效、健壮异步代码的关键。一开始可能会觉得有点抽象,但随着实践的深入,你会发现它强大的调度能力。

async/await

在实际应用中,如何处理并发任务的异常和取消操作?

在实际的异步编程中,仅仅启动和等待任务是不够的,我们还需要妥善处理可能出现的异常和任务取消。这就像在现实世界中,计划总赶不上变化,我们需要有应对突发状况的机制。

1. 异常处理

异步任务中的异常处理与同步代码类似,主要通过

try...except

块来实现。

捕获单个协程的异常: 你可以在

await

一个可能抛出异常的协程时,使用

try...except

来捕获它。

import asyncioasync def might_fail_task(task_id):    if task_id % 2 != 0:        raise ValueError(f"任务 {task_id} 故意失败了!")    await asyncio.sleep(1)    return f"任务 {task_id} 成功完成。"async def handle_single_task_error():    try:        result = await might_fail_task(1) # 这个会失败        print(result)    except ValueError as e:        print(f"捕获到异常: {e}")    try:        result = await might_fail_task(2) # 这个会成功        print(result)    except ValueError as e:        print(f"捕获到异常: {e}") # 不会执行# asyncio.run(handle_single_task_error())

asyncio.gather()

中的异常:

asyncio.gather()

在处理多个任务时,默认行为是“快速失败”(fail fast)。如果它所等待的任何一个子任务抛出了异常,

gather

会立即取消所有其他未完成的任务,并重新抛出第一个遇到的异常。

async def handle_gather_error_default():    try:        results = await asyncio.gather(            might_fail_task(1), # 会失败            might_fail_task(2), # 会成功,但可能被取消            might_fail_task(3)  # 会失败,但可能在第一个异常抛出前被取消        )        print(f"所有任务完成: {results}")    except ValueError as e:        print(f"asyncio.gather 捕获到异常: {e}")        # 此时,其他任务可能已经被取消了# asyncio.run(handle_gather_error_default())

asyncio.gather()

return_exceptions=True

如果你希望

gather

等待所有任务完成,即使其中有任务失败,并将异常作为结果返回而不是直接抛出,你可以设置

return_exceptions=True

async def handle_gather_error_return_exceptions():    results = await asyncio.gather(        might_fail_task(1), # 失败        might_fail_task(2), # 成功        might_fail_task(3), # 失败        return_exceptions=True # 关键参数    )    print("所有任务都处理完毕(包括失败的):")    for i, res in enumerate(results):        if isinstance(res, Exception):            print(f"  任务 {i+1} 失败: {res}")        else:            print(f"  任务 {i+1} 成功: {res}")# asyncio.run(handle_gather_error_return_exceptions())

这种方式在处理多个独立且不互相依赖的任务时非常有用,比如批量处理数据,你希望即使某些数据处理失败,也不影响其他数据的处理。

2. 任务取消

在异步编程中,任务取消是一个重要的概念。你可能需要停止一个长时间运行的任务,例如用户关闭了一个页面,或者一个操作超时了。

task.cancel()

调用一个

Task

对象的

cancel()

方法,会向该任务发送一个取消请求。这并不会立即停止任务,而是在任务内部(通常是在下一个

await

点)抛出一个

asyncio.CancelledError

异常。

async def long_running_task(task_name):    print(f"[{task_name}] 任务开始...")    try:        for i in range(5):            print(f"[{task_name}] 工作中... {i+1}/5")            await asyncio.sleep(1) # 这是一个可能的取消点    except asyncio.CancelledError:        print(f"[{task_name}] 任务被取消了!执行清理工作...")    finally:        print(f"[{task_name}] 任务结束(无论成功或取消)。")async def main_cancel():    task = asyncio.create_task(long_running_task("A"))    await asyncio.sleep(2.5) # 等待任务运行一段时间    print("主程序:请求取消任务 A...")    task.cancel() # 发送取消请求    try:        await task # 等待

以上就是Python怎么使用async/await_Python异步编程async/await入门的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1372757.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 12:35:41
下一篇 2025年12月14日 12:35:52

相关推荐

  • Python教程:解决比较大小程序中字符串比较导致的问题

    本教程旨在帮助初学者理解和解决在编写Python程序时,由于字符串比较和类型转换不当导致逻辑错误的问题。通过分析一个寻找最大值和最小值的程序示例,我们将深入探讨字符串比较的特性,并提供正确的代码实现方案,同时介绍一些Python编程的最佳实践。 问题分析 初学者在尝试编写一个程序,该程序循环接收用户…

    好文分享 2025年12月14日
    000
  • 使用 WSL(Windows 子系统)运行 Python 的优势

    WSL提供与生产环境一致的Linux开发体验,避免路径、权限差异问题;支持Unix工具链和依赖管理,简化Python库安装;性能接近原生Linux,多进程和文件I/O表现优异;可无缝集成VS Code等Windows工具,实现高效协作。 在 Windows 上使用 WSL(Windows Subsy…

    2025年12月14日
    000
  • Python 异常处理在分布式系统中的挑战

    传统的异常处理在分布式系统中失效,因其无法应对网络不可靠、服务独立性及状态不一致问题。1. 分布式环境存在超时、崩溃、资源耗尽等系统级故障,错误不再非成功即失败;2. 盲目重试可能导致重复操作或雪崩效应;3. 需采用幂等性设计、指数退避重试、断路器模式、超时控制和消息队列解耦;4. 结合分布式追踪、…

    2025年12月14日
    000
  • Python初学者指南:正确在命令行运行Python脚本与解决cd命令错误

    本文旨在指导Python初学者正确在Windows命令行环境中运行Python脚本,并解决在Python交互式解释器中误用系统命令(如cd)导致的SyntaxError。核心在于区分系统命令提示符(CMD/PowerShell)与Python交互式解释器,理解各自的功能,从而避免常见的操作错误,确保…

    2025年12月14日
    000
  • Python数据划分策略:在指定子集大小下实现均值均衡

    本文探讨如何在给定超集和预设子集大小的情况下,将超集元素无重复地划分到多个子集中,以使每个子集的均值尽可能接近超集的均值。文章将介绍如何将此问题建模为集合划分问题,并提供基于线性规划(使用PuLP库)的精确求解方案,同时探讨启发式算法如Karmarkar-Karp的适用性及性能考量,旨在为高效、公平…

    2025年12月14日
    000
  • 生成准确表达文章主题的标题 Python中计算阶乘末尾零的原理与高效方法

    本文深入探讨了在Python中计算给定数字阶乘末尾零的有效方法。文章首先指出直接计算阶乘并转换为字符串统计零的局限性,特别是对于大数阶乘可能导致的溢出问题。随后,详细阐述了基于数学原理(勒让德公式)的高效算法,并通过示例代码演示了其实现。最后,补充了针对任意数字字符串末尾零计数的通用方法,并强调了不…

    2025年12月14日
    000
  • 在pydrake场景YAML中优雅地引用本地SDF文件

    本文旨在解决pydrake项目中在场景YAML文件中引用本地SDF文件时,避免使用绝对路径和维护困难的问题。通过创建本地包并配置package.xml,开发者可以像引用标准包一样,使用package://语法简洁高效地管理和引用自定义SDF模型,从而提升项目可维护性和团队协作效率。 在pydrake…

    2025年12月14日
    000
  • Pydrake中本地SDF文件引用的最佳实践:构建本地包

    本文介绍了在pydrake场景YAML文件中优雅地引用本地SDF文件的方法。针对传统绝对路径难以维护和动态生成YAML的繁琐,教程提供了一种创建本地包的解决方案。通过在SDF文件目录中添加package.xml,开发者可以利用package:// URI便捷地管理和引用自定义模型,从而提高项目可维护…

    2025年12月14日
    000
  • Python中高效计算阶乘末尾零的方法详解

    Python中高效计算阶乘末尾零的方法详解。本文深入探讨了如何在Python中高效计算给定数字阶乘的末尾零数量。通过分析阶乘末尾零的数学原理,揭示了其与因子5数量的直接关系。文章首先指出直接计算阶乘和字符串遍历的低效与潜在问题,随后重点介绍了基于勒让德公式(Legendre’s Form…

    2025年12月14日
    000
  • pydrake场景YAML中引用本地SDF文件的优雅方案:构建本地包

    本文介绍在pydrake场景YAML文件中优雅地引用本地SDF文件的解决方案。针对传统方法中绝对路径难以维护、动态生成YAML文件不便的问题,我们提出通过创建本地包(local package)的方式。只需在SDF文件所在目录添加一个package.xml文件,即可使用package://协议简洁高…

    2025年12月14日
    000
  • 神经网络输出形状操作:多维输入数据的处理策略

    本文旨在解决Keras Dense层在处理多维输入时输出形状不符合预期的问题,特别是当模型需要生成二维向量输出(如DQN模型)时。我们将深入探讨Dense层的工作机制,解释为何会出现三维输出,并提供使用tf.keras.layers.Flatten进行模型架构调整的有效解决方案,确保模型输出符合下游…

    2025年12月14日
    000
  • 理解Keras Dense层多维输入与输出:DQN模型形状操控指南

    本教程深入探讨Keras Dense层处理多维输入时的行为,解释为何其输出可能呈现多维结构。针对深度Q网络(DQN)等需要特定一维输出形状的场景,文章提供了详细的解决方案,包括如何通过Flatten层调整网络架构,确保模型输出符合预期,避免因形状不匹配导致的错误。 Keras Dense层对多维输入…

    2025年12月14日
    000
  • PyInstaller与外部文件依赖管理:确保可执行文件正确访问数据

    本文旨在解决使用PyInstaller打包Python程序为可执行文件(.exe)后,程序无法找到外部数据文件(如文本文件、图片等)的问题。核心解决方案在于理解PyInstaller的工作原理以及程序默认的文件查找路径,并确保可执行文件与所有非脚本依赖文件部署在同一目录下,或通过相对路径正确引用。 …

    2025年12月14日
    000
  • 初学者如何配置 Python 开发 IDE(VS Code、PyCharm)

    初学者配置Python开发环境首选VS Code或PyCharm。先从python.org安装Python并添加到PATH,验证安装后,在VS Code中安装官方Python扩展,选择解释器并运行.py文件,可选装pylint和black工具;PyCharm用户则下载Community版,设置项目解…

    2025年12月14日
    000
  • Python:从生成器函数返回列表

    本文旨在解决如何将一个计算加法的函数转换为生成器,使其能够分批次返回结果列表。我们将探讨如何正确实现生成器函数,并提供一个可配置批次大小的示例,确保所有计算结果都能被正确处理并返回。 使用生成器函数分批次返回结果 在Python中,生成器是一种特殊的函数,它使用 yield 关键字来逐步产生值,而不…

    2025年12月14日
    000
  • Pandas DataFrame 高级合并技巧:处理共有与独有键的策略

    本教程深入探讨如何在Pandas中高效合并两个DataFrame,以实现对共有键的数据进行更新和扩展,同时将独有键的数据作为新行添加。我们将详细介绍使用 DataFrame.join(how=’outer’) 和 DataFrame.combine_first() 两种方法,…

    2025年12月14日
    000
  • Python模块导入策略:直接引用类与避免命名空间前缀

    本教程深入探讨Python中导入模块的不同策略,重点介绍如何避免在引用类名时添加模块前缀。文章将详细阐述标准导入、直接导入特定名称(from module import name)以及通配符导入(from module import *)的机制、优缺点及适用场景,并提供实践建议,帮助开发者编写更清晰…

    2025年12月14日
    000
  • Python str()函数整数转换行为解析:避免字符串包含判断中的隐式陷阱

    本文深入探讨了Python中str()函数对整数进行转换时的行为特性,特别是对于带有前导零的整数(如000)。我们揭示了str(000)为何会返回字符串”0″而非”000″,并分析了这一特性在字符串包含判断中可能导致的意外结果。通过实例代码和最佳实践,…

    2025年12月14日
    000
  • Pandas DataFrame智能合并:兼顾共同键更新与非共同键新增

    本教程深入探讨如何使用Pandas高效合并两个DataFrame,实现共同键数据的列更新与非共同键数据的新增行。我们将详细解析DataFrame.join和DataFrame.combine_first两种方法,通过设置公共列为索引并结合适当的合并策略,演示如何实现这种复杂的智能合并,确保数据完整性…

    2025年12月14日
    000
  • Pandas DataFrames 高级合并技巧:处理共同键更新与新增行

    本教程深入探讨Pandas DataFrames的复杂合并策略,旨在解决如何优雅地合并两个DataFrame,实现共同键数据行的更新与扩展,同时保留并添加不共享键的独立行。我们将重点介绍并演示 DataFrame.join(how=’outer’) 和 DataFrame.c…

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信