,

python异步编程

异步生成器与异步迭代器 在协程函数中使用 yield,就变成了异步生成器。它每次 yield 产出值时都可以 …

异步生成器与异步迭代器

在协程函数中使用 yield,就变成了异步生成器。它每次 yield 产出值时都可以 await 其他协程。

async def async_generator():
   for i in range(3):
       await asyncio.sleep(1)
       yield i


async def main():
   # 必须用 async for 来遍历异步生成器
   async for item in async_generator():
       print(item)  # 每隔 1 秒打印一个数字


asyncio.run(main())
同样可以自定义**异步迭代器**,实现 `__aiter__` 和 `__anext__` 两个协议方法:
class AsyncCounter:
    def __init__(self, limit: int):
        self._limit = limit
        self._count = 0

    def __aiter__(self):
        return self

    async def __anext__(self) -> int:
        self._count += 1
        if self._count > self._limit:
            raise StopAsyncIteration
        await asyncio.sleep(0.5)
        return self._count


async def main():
    async for num in AsyncCounter(5):
        print(num)  # 每隔 0.5 秒打印 1 2 3 4 5

对比同步迭代协议:

同步异步
__iter__ / __next____aiter__ / __anext__
for x in iterableasync for x in async_iterable
StopIterationStopAsyncIteration
生成器 yield异步生成器 async def + yield

异步上下文管理器

async with 背后是 __aenter____aexit__ 两个协议方法,和同步的 with 类似,只是都是异步的。

class AsyncResource:
   async def __aenter__(self):
       print("获取资源")
       await asyncio.sleep(0.5)
       return self

   async def __aexit__(self, exc_type, exc_val, exc_tb):
       print("释放资源")
       await asyncio.sleep(0.5)


async def main():
   async with AsyncResource() as res:
       print("使用资源")

对比同步上下文管理器:

同步异步
__enter__ / __exit____aenter__ / __aexit__
with ctxasync with ctx

官方 asyncio 核心 API

sleep —— 非阻塞等待

使用 loop.call_later + Future 实现的延时:

def async_delay(duration: int):
   loop = asyncio.get_event_loop()
   future = loop.create_future()
   loop.call_later(duration, future.set_result, None)
   return future

官方直接提供了 asyncio.sleep,用法完全相同:

async def main():
   print("开始")
   await asyncio.sleep(1)  # 挂起当前协程 1 秒,事件循环去调度其他协程
   print("1秒后")

创建与运行协程

import asyncio


async def say_hello():
   await asyncio.sleep(1)
   return "Hello"


async def main():
   # 1. asyncio.run —— 最高层入口
   pass

result = asyncio.run(say_hello())

create_task —— 将协程包装成 Task 并调度执行

async def main():
   # 创建 Task,协程会立即被调度到事件循环中执行
   task = asyncio.create_task(say_hello())
   # 这里可以做别的事,task 已经在后台运行
   result = await task  # 等待 task 完成
   print(result)

gather —— 并发执行多个协程

async def fetch(url: str, delay: int) -> str:
   await asyncio.sleep(delay)
   return f"{url} 完成"


async def main():
   # 同时发起多个请求,等待所有完成
   results = await asyncio.gather(
       fetch("url1", 2),
       fetch("url2", 1),
       fetch("url3", 3),
  )
   print(results)  # ['url1 完成', 'url2 完成', 'url3 完成']


asyncio.run(main())

gather 的特点:

  • 所有协程并发执行
  • 返回结果顺序与传入顺序一致
  • 任何一个协程抛出异常,gather 会立即传播异常(其他协程仍会继续运行)
  • 可通过 return_exceptions=True 让异常以结果形式返回,不中断 gather
async def fail() -> str:
   raise ValueError("出错了")


async def main():
   results = await asyncio.gather(
       fetch("ok", 1),
       fail(),
       return_exceptions=True,  # 将异常作为返回值,不抛出
  )
   print(results)  # ['ok 完成', ValueError('出错了')]

wait —— 更灵活的等待方式

import asyncio
from typing import Coroutine


async def main():
   tasks = [
       asyncio.create_task(fetch("A", 2)),
       asyncio.create_task(fetch("B", 1)),
       asyncio.create_task(fetch("C", 3)),
  ]

   # FIRST_COMPLETED: 任一完成就返回
   # FIRST_EXCEPTION: 任一异常就返回
   # ALL_COMPLETED: 全部完成(默认)
   done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
   print(f"已完成: {len(done)}, 待完成: {len(pending)}")

   # 还可以手动处理未完成的任务
   for task in pending:
       task.cancel()

as_completed —— 谁先完成谁先处理

async def main():
   coros = [fetch("A", 2), fetch("B", 1), fetch("C", 3)]

   for coro in asyncio.as_completed(coros):
       result = await coro
       print(result)  # 按照完成的先后顺序打印

TaskGroup —— 结构化并发(Python 3.11+)

async def main():
   # TaskGroup 保证所有子任务在退出前完成
   # 如果某个子任务异常,会取消组内所有其他任务
   async with asyncio.TaskGroup() as tg:
       task1 = tg.create_task(fetch("A", 2))
       task2 = tg.create_task(fetch("B", 1))
       task3 = tg.create_task(fetch("C", 3))

   # 到这里所有任务都已安全完成
   print(task1.result(), task2.result(), task3.result())

Lock —— 互斥锁

多个协程可能竞争共享资源,Lock 保证同一时刻只有一个协程能访问

import asyncio

shared_data: int = 0
lock = asyncio.Lock()


async def safe_increment():
   global shared_data
   async with lock:  # 获取锁,等锁释放前其他协程会在此等待
       temp = shared_data
       await asyncio.sleep(0)  # 模拟耗时操作,此时切换协程也不会出问题
       shared_data = temp + 1


async def main():
   await asyncio.gather(*[safe_increment() for _ in range(100)])
   print(shared_data)  # 100

Event —— 事件通知

一个协程等待另一个协程发出信号

async def waiter(event: asyncio.Event):
   print("waiter: 开始等待")
   await event.wait()  # 等待事件被设置
   print("waiter: 被唤醒")


async def setter(event: asyncio.Event):
   print("setter: 1秒后设置事件")
   await asyncio.sleep(1)
   event.set()  # 设置事件,唤醒所有等待者


async def main():
   event = asyncio.Event()
   await asyncio.gather(waiter(event), setter(event))
waiter: 开始等待
setter: 1秒后设置事件
waiter: 被唤醒

Semaphore —— 限制并发数

semaphore = asyncio.Semaphore(3)  # 同时最多 3 个


async def limited_fetch(url: str):
   async with semaphore:  # 超过并发限制时等待
       print(f"开始请求 {url}")
       await asyncio.sleep(1)
       print(f"完成请求 {url}")
       return url


async def main():
   urls = [f"url{i}" for i in range(10)]
   await asyncio.gather(*[limited_fetch(url) for url in urls])

Queue —— 异步队列

生产者-消费者模式的基石

import random


async def producer(queue: asyncio.Queue):
    for i in range(10):
        item = f"item_{i}"
        await queue.put(item)
        print(f"生产: {item}")
        await asyncio.sleep(random.random())
    await queue.put(None)  # 发送结束信号


async def consumer(name: str, queue: asyncio.Queue):
    while True:
        item = await queue.get()
        if item is None:  # 收到结束信号
            queue.task_done()
            break
        print(f"{name} 消费: {item}")
        queue.task_done()


async def main():
    queue = asyncio.Queue(maxsize=5)
    await asyncio.gather(
        producer(queue),
        consumer("C1", queue),
        consumer("C2", queue),
    )

asyncio.wait_for

异步编程中用于控制任务执行时间的核心函数。它允许你等待一个协程或任务(Task)完成,但前提是必须在指定的超时时间(timeout)内完成。

  1. 超时取消:如果在设定的 timeout 秒数内,等待的对象(awaitable)没有执行完毕,wait_for 会主动取消该任务,并抛出 asyncio.TimeoutError 异常。
  2. 正常返回:如果任务在超时前顺利完成,wait_for 会返回该任务的执行结果。
  3. 异常传播:如果等待的任务在执行过程中抛出了其他未处理的异常,该异常会直接传播给调用者。
async def slow_operation():
    await asyncio.sleep(10)
    return "完成"


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

asyncio.timeout(Python 3.11+)

async def main():
   try:
       async with asyncio.timeout(2):
           result = await slow_operation()
   except TimeoutError:
       print("操作超时了")

asyncio.timeout 是 Python 3.11 引入的现代超时控制机制,作为上下文管理器(Context Manager)提供。相比早期的 asyncio.wait_for(),它提供了更优雅、更灵活的任务超时管理方式。

在异步中运行同步代码

import time


def blocking_io() -> str:
   time.sleep(0.5)  # 同步阻塞操作
   return "文件读取完成"


def cpu_intensive() -> int:
   return sum(i * i for i in range(10_000_000))


async def main():
   # to_thread:将同步阻塞函数放到线程池中执行
   result = await asyncio.to_thread(blocking_io)
   print(result)

   # run_in_executor:更底层,可以指定执行器
   loop = asyncio.get_running_loop()
   result = await loop.run_in_executor(None, cpu_intensive)
   print(result)

1. IO 密集型同步代码(如网络请求、文件读写)

绝对不要直接在协程中调用(例如直接使用 requests.get() 或 time.sleep()),这会阻塞当前线程的事件循环。

解决方案:将同步函数扔到线程池中执行

现代写法(Python 3.9+):使用 asyncio.to_thread()。它本质上是 run_in_executor 的简化版,无需手动获取事件循环,一行代码即可将同步函数包装为可 await 的协程。

import asyncio
import requests

async def fetch_data():
    # 将同步的 requests.get 扔给默认线程池执行
    response = await asyncio.to_thread(requests.get, 'http://example.com')
    return response.status_code

传统写法(兼容老版本):使用 loop.run_in_executor(None, sync_func, args)

loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, requests.get, 'http://example.com')

第三方异步库

网络请求

aiohttp(第三方最流行的异步 HTTP 库)

import aiohttp


async def fetch_url(url: str) -> str:
   async with aiohttp.ClientSession() as session:
       async with session.get(url) as response:
           return await response.text()


async def main():
   html = await fetch_url("https://example.com")
   print(len(html))

httpx(支持同步/异步双模式,API 更友好)

import httpx


async def fetch_url(url: str) -> str:
   async with httpx.AsyncClient() as client:
       response = await client.get(url)
       return response.text


async def main():
   html = await fetch_url("https://example.com")
   print(len(html))

文件 I/O

aiofiles(异步文件操作)

import aiofiles


async def read_write_example():
   # 写文件
   async with aiofiles.open("example.txt", "w") as f:
       await f.write("Hello, 异步文件!\n")

   # 读文件
   async with aiofiles.open("example.txt", "r") as f:
       content = await f.read()
       print(content)

Previous Post

Next Post

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor