Python异步编程:asyncio深入理解与实战

By | 2026年7月22日

前言

在I/O密集型应用中,Python的异步编程(asyncio)可以显著提升性能。然而,许多开发者对asyncio的理解停留在表面,导致代码效率低下或出现难以调试的问题。本文将从事件循环、协程、任务和Future出发,深入剖析asyncio的工作原理,并通过实战案例展示最佳实践。

1. 事件循环:异步的引擎

事件循环是asyncio的核心,它负责调度和执行协程。Python 3.10+推荐使用asyncio.run()来启动事件循环。


import asyncio

async def main():
    print('Hello')
    await asyncio.sleep(1)
    print('World')

asyncio.run(main())

注意asyncio.run()会创建新的事件循环,并在完成后关闭它。不要手动调用loop.close(),除非你有特殊需求。

底层事件循环操作

如果需要更精细的控制,可以使用loop = asyncio.neweventloop()loop.rununtilcomplete(),但通常不推荐。

2. 协程:async/await语法

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


async def fetch_data():
    print("开始获取数据")
    await asyncio.sleep(2)
    print("数据获取完成")
    return {"data": 42}

# 正确执行
result = asyncio.run(fetch_data())
print(result)  # 输出: {'data': 42}

常见错误:忘记await,导致协程对象未被执行。


# 错误示例
async def main():
    fetch_data()  # 协程对象被创建但未执行

asyncio.run(main())

3. 任务:并发执行协程

任务是协程的封装,用于并发执行。使用asyncio.create_task()创建任务。


async def task1():
    await asyncio.sleep(2)
    return "Task1 done"

async def task2():
    await asyncio.sleep(1)
    return "Task2 done"

async def main():
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())
    
    # 等待所有任务完成
    results = await asyncio.gather(t1, t2)
    print(results)

asyncio.run(main())

输出['Task1 done', 'Task2 done'],总耗时约2秒(并发执行)。

任务取消与超时

使用asyncio.wait_for()设置超时,或task.cancel()取消任务。


async def slow_operation():
    await asyncio.sleep(10)
    return "完成"

async def main():
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=3)
    except asyncio.TimeoutError:
        print("操作超时")

asyncio.run(main())

注意:超时后任务会被取消,并抛出CancelledError。如果需要清理资源,可以捕获CancelledError

4. Future:底层异步结果

Future是表示异步操作结果的类,类似于JavaScript的Promise。通常我们不直接使用Future,而是通过ensurefuture()loop.createfuture()创建。


async def set_future(fut):
    await asyncio.sleep(1)
    fut.set_result("Future resolved")

async def main():
    loop = asyncio.get_running_loop()
    fut = loop.create_future()
    asyncio.create_task(set_future(fut))
    result = await fut
    print(result)

asyncio.run(main())

理解await fut会挂起当前协程,直到Future被设置结果。

5. 实战:异步HTTP请求

使用aiohttp库进行异步HTTP请求(需安装:pip install aiohttp)。


import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = ["https://httpbin.org/delay/1", "https://httpbin.org/delay/2", "https://httpbin.org/delay/3"]
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
        pages = await asyncio.gather(*tasks)
        print(f"获取了 {len(pages)} 个页面")

asyncio.run(main())

性能对比:同步版本需要约6秒,异步版本仅需约3秒(取决于网络延迟)。

6. 常见陷阱与最佳实践

陷阱1:阻塞事件循环

在协程中不要使用time.sleep(),它会阻塞整个事件循环。使用asyncio.sleep()


# 错误
async def bad():
    time.sleep(1)  # 阻塞事件循环

# 正确
async def good():
    await asyncio.sleep(1)

陷阱2:忘记await

确保所有协程都被await或作为任务调度。

陷阱3:共享状态

异步代码中共享可变状态可能导致竞态条件。使用asyncio.Lock保护临界区。


lock = asyncio.Lock()

async def critical_section():
    async with lock:
        # 访问共享资源
        pass

最佳实践:使用asyncio.run()

始终使用asyncio.run()作为入口,它负责创建和关闭事件循环。

最佳实践:合理使用gather与wait

  • asyncio.gather():返回所有结果,如果某个任务异常,会立即抛出。
  • asyncio.wait():更灵活,可以等待所有、第一个完成、第一个异常等。

7. 总结

本文介绍了asyncio的核心概念:事件循环、协程、任务和Future,并通过实战案例展示了异步HTTP请求。关键要点:

  • 使用async/await定义协程
  • asyncio.create_task()并发执行
  • 避免阻塞事件循环
  • 使用asyncio.run()管理事件循环

延伸阅读

  • asyncio官方文档:https://docs.python.org/zh-cn/3/library/asyncio.html
  • 异步编程模式:回调、Future、协程对比

希望本文能帮助你深入理解asyncio,写出高效可靠的异步Python代码。