深入理解 Python 的 async 和 await

在现代软件开发中,异步编程已成为提高应用程序性能的关键手段,尤其是在处理 I/O 密集型任务时。Python 提供了强大的 asyncawait 关键字,使得编写异步代码更加直观和高效。在本文中,我们将深入探讨 Python 中的 asyncawait,结合实例、协程的概念,以及底层原理。


一、协程的概念

1.1 什么是协程?

协程(Coroutine)是一种 协作式的并发计算。与传统的多线程并发不同,协程通过主动让出执行权来实现任务的切换,而不是依赖操作系统调度。

在 Python 中,协程是由 协程函数 定义的,并通过 async def 关键字实现。协程不会立即执行,而是返回一个协程对象,表示任务的执行逻辑和状态。协程对象的执行需要借助 事件循环 来调度。

1.2 协程的优点
  • 轻量级:协程运行在单线程中,不需要线程切换带来的开销。
  • 非阻塞:在等待 I/O 时,其他协程可以继续执行。
  • 适用于 I/O 密集型任务:比如文件操作、网络请求等。

  • 二、asyncawait 的基础用法

    2.1 定义协程函数

    协程函数是使用 async def 定义的函数,调用它时不会立即执行,而是返回一个协程对象。

    import asyncio
    
    # 定义一个协程函数
    async def say_hello():
        print("Hello!")
        await asyncio.sleep(1)  # 模拟异步等待
        print("Goodbye!")
    
    2.2 调用和执行协程

    协程需要通过 await 或事件循环来执行。直接调用协程函数只会返回一个协程对象。

    # 错误示例:直接调用协程函数
    coro = say_hello()
    print(coro)  # <coroutine object say_hello at 0x...>
    
    # 正确示例:通过 asyncio.run 执行协程
    asyncio.run(say_hello())
    
    2.3 await 的作用

    await 关键字用于暂停协程的执行,等待一个可等待对象完成,并返回结果。可等待对象包括协程对象、asyncio 提供的异步操作(如 asyncio.sleep)等。

    async def delayed_hello():
        print("Waiting for 2 seconds...")
        await asyncio.sleep(2)
        print("Done!")
    

    示例:

    import asyncio
    
    # 定义一个协程函数
    async def say_hello():
        print("Hello!")
        await asyncio.sleep(1)  # 模拟异步等待
        print("Goodbye!")
    
    async def say_hi():
        print("Waiting for 2 seconds...")
        await asyncio.sleep(2)
        print("Done!")
    
    async def say():
        await say_hello()
        await say_hi()
    
    asyncio.run(say())
    

    三、协程的原理与事件循环

    3.1 协程对象是什么?

    当调用一个协程函数时,它不会执行,而是返回一个 协程对象。协程对象保存了协程的执行逻辑和当前状态,只有在事件循环中调度时才会真正运行。

    import asyncio
    
    async def example():
        await asyncio.sleep(1)
    
    coro = example()
    print(type(coro))  # <class 'coroutine'>
    

    可以使用 asyncio.iscoroutine() 来判断某个对象是否为协程对象。

    3.2 事件循环

    事件循环是 asyncio 模块的核心,用于管理和调度协程的执行。asyncio.run() 是高层的事件循环管理器,它会:

    1. 创建一个新的事件循环。
    2. 执行传入的协程,调度所有任务。
    3. 关闭事件循环,清理资源。

    示例:

    async def task1():
        print("Task 1 started")
        await asyncio.sleep(1)
        print("Task 1 completed")
    
    async def task2():
        print("Task 2 started")
        await asyncio.sleep(2)
        print("Task 2 completed")
    
    async def main():
        await asyncio.gather(task1(), task2())  # 并发执行多个任务
    
    # 启动事件循环
    asyncio.run(main())
    

    输出:

    Task 1 started
    Task 2 started
    Task 1 completed
    Task 2 completed
    

    3.3 asyncio.run() 的底层原理

    asyncio.run() 的核心逻辑如下:

    1. 检查当前线程是否已有运行的事件循环,防止嵌套。
    2. 创建新的事件循环并将协程添加到其中。
    3. 使用 loop.run_until_complete() 执行协程,阻塞主线程直到完成。
    4. 关闭事件循环,释放资源。

    代码示意:

    def asyncio_run(coro):
        loop = asyncio.new_event_loop()  # 创建新的事件循环
        asyncio.set_event_loop(loop)
        try:
            return loop.run_until_complete(coro)  # 执行协程
        finally:
            loop.close()  # 关闭事件循环
    


    四、实例:并发处理多个任务

    假设我们需要同时处理多个异步任务,例如向多个 API 发送请求。可以使用 asyncio.gather 来并发执行多个协程。

    import asyncio
    
    async def fetch_data(api):
        print(f"Fetching data from {api}...")
        await asyncio.sleep(2)  # 模拟网络延迟
        print(f"Data from {api} received!")
    
    async def main():
        apis = ["API_1", "API_2", "API_3"]
        # 并发执行多个协程
        await asyncio.gather(*(fetch_data(api) for api in apis))
    
    asyncio.run(main())
    

    输出:

    Fetching data from API_1...
    Fetching data from API_2...
    Fetching data from API_3...
    Data from API_1 received!
    Data from API_2 received!
    Data from API_3 received!
    

    五、注意事项与最佳实践

    5.1 注意事项
    1. 不能嵌套调用 asyncio.run()asyncio.run() 会创建一个新的事件循环,嵌套调用会引发 RuntimeError
    2. 协程不能直接调用:协程必须通过 await 或事件循环执行,直接调用只会返回协程对象。
    3. 事件循环的生命周期:确保事件循环在程序退出前正确关闭,防止资源泄漏。
    5.2 最佳实践
  • 使用 asyncio.run() 作为程序的入口,统一管理事件循环。
  • 使用 asyncio.gather()asyncio.create_task() 并发执行多个任务。
  • 在协程中处理异常,避免未捕获的错误导致程序崩溃。

  • 六、总结

    Python 的 asyncawait 提供了一种优雅的方式来实现异步编程。通过协程和事件循环,开发者可以高效地处理 I/O 密集型任务,而无需引入复杂的线程或进程管理。

    作者:花千树-010

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入理解 Python 的 async 和 await

    发表回复