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

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何解决本地图片在使用 mask JS 库时出现的跨域错误?

    如何跨越localhost使用本地图片? 问题: 在本地使用mask js库时,引入本地图片会报跨域错误。 解决方案: 要解决此问题,需要使用本地服务器启动文件,以http或https协议访问图片,而不是使用file://协议。例如: python -m http.server 8000 然后,可以…

    2025年12月24日
    200
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • 为什么自定义样式表在 Safari 中访问百度页面时无法生效?

    自定义样式表在 safari 中失效的原因 用户尝试在 safari 偏好设置中添加自定义样式表,代码如下: body { background-image: url(“/users/luxury/desktop/wallhaven-o5762l.png”) !important;} 测试后发现,在…

    2025年12月24日
    000
  • 使用 Mask 导入本地图片时,如何解决跨域问题?

    跨域疑难:如何解决 mask 引入本地图片产生的跨域问题? 在使用 mask 导入本地图片时,你可能会遇到令人沮丧的跨域错误。为什么会出现跨域问题呢?让我们深入了解一下: mask 框架假设你以 http(s) 协议加载你的 html 文件,而当使用 file:// 协议打开本地文件时,就会产生跨域…

    2025年12月24日
    200
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200

发表回复

登录后才能评论
关注微信