10. 协程编程#

在Python中,async和await是用于定义和调用协程的关键字,它们是Python 3.5及以上版本中引入的异步编程特性的一部分。异步编程允许你编写非阻塞的代码,这对于I/O密集型和高延迟的操作(如网络请求、文件读写等)非常有用。

10.1. async关键字#

async用于定义一个协程。它可以放在一个函数定义前,使得该函数成为一个异步函数(也称为协程)。异步函数在被调用时不会立即执行,而是返回一个协程对象,该对象需要被await或在事件循环中执行。

定义异步函数的示例:

async def fetch_data():
    await asyncio.sleep(1)  # 模拟一个异步操作
    return {'data': 1}

10.2. await关键字#

await用于暂停执行一个协程,直到一个可等待对象(如协程、任务或者Future)完成。它只能在异步函数内部使用。 使用await的示例:

async def main():
    data = await fetch_data()  # 等待fetch_data协程完成
    print(data)

在这个例子中,main函数中的await fetch_data()会暂停main的执行,直到fetch_data协程完成并返回结果。

完整的异步编程示例

import asyncio

async def fetch_data():
    print("开始获取数据...")
    await asyncio.sleep(2)  # 模拟一个耗时的异步操作
    print("数据获取完成")
    return {'data': 1}

async def main():
    print("开始执行")
    data = await fetch_data()  # 等待fetch_data协程完成
    print("获取到的数据:", data)

# 运行事件循环
asyncio.run(main())

上面代码不能运行在jupyter中。

在这个例子中,asyncio.run(main())会启动事件循环,执行main协程。main协程内部等待fetch_data协程完成,并在完成后继续执行。 注意事项

  • 只能在异步函数内部使用await:如果你尝试在普通函数中使用await,会引发语法错误。

  • async定义的函数返回协程对象:当你定义一个异步函数并调用它时,它不会立即执行,而是返回一个协程对象,你需要使用await来启动它,或者在事件循环中运行。

  • 事件循环:异步编程需要一个事件循环来驱动协程的执行。asyncio.run()是一个方便的函数,它创建一个新的事件循环,运行传入的协程,然后关闭事件循环。

  • 错误处理:在异步函数中,你可以使用try/except块来捕获异常,就像在同步代码中一样。

  • 协程的组合:可以使用asyncio.gather()来并发运行多个协程。

异步编程是一个复杂的主题,涉及到事件循环、任务、Future对象等多个概念。掌握这些概念可以帮助你编写更高效、更可读的异步代码。

10.3. asyncio包#

asyncio 是 Python 的标准库之一,用于编写单线程的并发代码,使用 async/await 语法。它允许你以异步的方式执行多个操作,这在处理 I/O 密集型任务时非常有用,比如网络请求、文件读写等。以下是一些 asyncio 的常用用法和示例:

10.3.1. 1. 运行协程#

最基本的用法是使用 asyncio.run() 来运行一个协程:

import asyncio

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

asyncio.run(main())

10.3.2. 2. 创建和等待协程任务#

你可以创建协程任务并使用 asyncio.wait() 来等待它们完成:

import asyncio

async def coro(name):
    print(f'Task {name} started')
    await asyncio.sleep(1)
    print(f'Task {name} finished')

async def main():
    task1 = asyncio.create_task(coro('A'))
    task2 = asyncio.create_task(coro('B'))
    
    await asyncio.wait([task1, task2])

asyncio.run(main())

10.3.3. 3. 并发运行多个协程#

使用 asyncio.gather() 来并发运行多个协程:

import asyncio

async def coro(name):
    print(f'Task {name} started')
    await asyncio.sleep(1)
    print(f'Task {name} finished')

async def main():
    await asyncio.gather(
        coro('A'),
        coro('B'),
        coro('C')
    )

asyncio.run(main())

10.3.4. 4. 超时控制#

使用 asyncio.wait_for() 来设置协程的超时时间:

import asyncio

async def coro(name):
    print(f'Task {name} started')
    await asyncio.sleep(2)
    print(f'Task {name} finished')

async def main():
    try:
        await asyncio.wait_for(coro('A'), timeout=1)
    except asyncio.TimeoutError:
        print('Task A timed out')

asyncio.run(main())

10.3.5. 5. 异步循环#

使用循环来并发执行多个协程:

import asyncio

async def coro(i):
    print(f'coro {i} started')
    await asyncio.sleep(0.1 * i)
    print(f'coro {i} finished')

async def main():
    tasks = [coro(i) for i in range(5)]
    await asyncio.gather(*tasks)

asyncio.run(main())

10.3.6. 6. 异步上下文管理器#

使用 async with 语句来管理异步上下文:

import asyncio

class AsyncContextManager:
    async def __aenter__(self):
        print('Entering context')
        return self

    async def __aexit__(self, exc_type, exc, tb):
        print('Exiting context')

async def main():
    async with AsyncContextManager() as manager:
        print('Inside context')

asyncio.run(main())

10.3.7. 7. 异步锁#

使用 asyncio.Lock() 来处理并发访问共享资源:

import asyncio

async def locked_coro(lock, x):
    async with lock:
        print(f'Task {x} obtained lock')
        await asyncio.sleep(1)
        print(f'Task {x} releasing lock')

async def main():
    lock = asyncio.Lock()
    tasks = [locked_coro(lock, i) for i in range(3)]
    await asyncio.gather(*tasks)

asyncio.run(main())

10.3.8. 8. 异步信号处理#

使用 asyncio.get_event_loop() 来处理信号:

import asyncio
import signal

async def main():
    loop = asyncio.get_running_loop()
    for signame in ('SIGINT', 'SIGTERM'):
        loop.add_signal_handler(getattr(signal, signame), lambda: print(f'Received {signame}'))

    print('Press Ctrl+C to stop')
    await asyncio.sleep(3600)  # 模拟长时间运行的任务

asyncio.run(main())

这些示例展示了 asyncio 的一些基本和高级用法,帮助你理解如何使用 asyncio 来编写异步代码。