# 异步编程实战指南

> 参考完整规范: `@/Python项目开发准则.md#10-异步编程规范`

## 快速判断：何时使用异步

| 场景 | 工具 | 何时用 |
|------|------|-------|
| **网络请求** | asyncio | I/O 密集，需要高并发 |
| **文件操作** | asyncio | 大文件处理，多文件并发 |
| **后台任务** | asyncio | 长时间运行，需要并发 |
| **CPU 计算** | multiprocessing | 计算密集，需要绕过 GIL |
| **线程共享** | threading | 需要共享内存的并发 |

## 基础异步函数

```python
import asyncio

# 基础异步函数
async def fetch_data(url: str) -> dict:
    """异步获取数据"""
    # 使用异步库（aiohttp）
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()

# 调用异步函数
result = asyncio.run(fetch_data("http://example.com"))
```

## 并发执行多个任务

### 方式 1: asyncio.gather()
```python
async def main():
    """并发执行多个任务"""
    urls = ["http://example1.com", "http://example2.com", "http://example3.com"]

    # 并发获取所有 URL
    results = await asyncio.gather(
        *[fetch_data(url) for url in urls]
    )
    return results

# 运行
results = asyncio.run(main())
```

### 方式 2: 限制并发数量
```python
async def fetch_with_limit(urls: list[str], max_concurrent: int = 5) -> list[dict]:
    """限制并发数量"""
    semaphore = asyncio.Semaphore(max_concurrent)

    async def bounded_fetch(url: str) -> dict:
        async with semaphore:
            return await fetch_data(url)

    return await asyncio.gather(*[bounded_fetch(url) for url in urls])

# 使用
results = asyncio.run(fetch_with_limit(urls, max_concurrent=3))
```

### 方式 3: 超时控制
```python
async def fetch_with_timeout(url: str, timeout: float = 30) -> dict | None:
    """添加超时控制"""
    try:
        return await asyncio.wait_for(
            fetch_data(url),
            timeout=timeout
        )
    except asyncio.TimeoutError:
        logger.warning(f"请求超时: {url}")
        return None
```

## 异步生成器

```python
async def stream_data(source: str) -> AsyncIterator[dict]:
    """流式处理数据"""
    async with aiofiles.open(source) as f:
        async for line in f:
            yield json.loads(line)

# 使用
async def process_stream():
    async for item in stream_data("data.jsonl"):
        print(item)

asyncio.run(process_stream())
```

## 异步上下文管理器

```python
class AsyncDatabaseConnection:
    """异步数据库连接"""

    async def __aenter__(self) -> "AsyncDatabaseConnection":
        """建立连接"""
        self.connection = await self.connect()
        return self

    async def __aexit__(self, *args) -> None:
        """关闭连接"""
        await self.connection.close()

    async def connect(self):
        """连接数据库"""
        return await asyncpg.connect(self.dsn)

# 使用
async def query_data():
    async with AsyncDatabaseConnection() as db:
        result = await db.query("SELECT * FROM users")
        return result
```

## 异步测试

```python
import pytest
from pytest_asyncio import fixture

@fixture
async def async_client():
    """异步客户端 fixture"""
    client = AsyncClient()
    await client.setup()
    yield client
    await client.teardown()

@pytest.mark.asyncio
async def test_fetch_data(async_client):
    """测试异步函数"""
    result = await async_client.fetch_data("http://example.com")
    assert result is not None

@pytest.mark.asyncio
async def test_concurrent_fetch():
    """测试并发"""
    results = await fetch_with_limit(["http://a.com", "http://b.com"])
    assert len(results) == 2
```

## 常见模式

### 模式 1: 异步任务队列
```python
async def task_queue(tasks: list, worker_count: int = 5):
    """使用队列处理任务"""
    queue = asyncio.Queue()

    # 添加任务到队列
    for task in tasks:
        await queue.put(task)

    # 并发处理
    async def worker():
        while not queue.empty():
            task = await queue.get()
            await process_task(task)
            queue.task_done()

    # 启动多个 worker
    await asyncio.gather(*[worker() for _ in range(worker_count)])
    await queue.join()

asyncio.run(task_queue(my_tasks, worker_count=3))
```

### 模式 2: 异步重试
```python
async def fetch_with_retry(
    url: str,
    max_retries: int = 3,
    backoff_factor: float = 2.0
) -> dict | None:
    """异步重试获取"""
    for attempt in range(max_retries):
        try:
            return await fetch_data(url)
        except Exception as e:
            if attempt == max_retries - 1:
                logger.error(f"最终失败: {url}")
                return None

            wait_time = backoff_factor ** attempt
            logger.warning(f"重试 {attempt + 1}/{max_retries}，等待 {wait_time}s")
            await asyncio.sleep(wait_time)
```

### 模式 3: 异步缓存
```python
from functools import lru_cache

@lru_cache(maxsize=128)
def cache_key(url: str) -> str:
    return url

cache: dict[str, dict] = {}

async def fetch_cached(url: str) -> dict:
    """带缓存的异步获取"""
    if url in cache:
        return cache[url]

    result = await fetch_data(url)
    cache[url] = result
    return result
```

## 最佳实践

✅ **推荐：**
- 使用 `asyncio.gather()` 并发多个任务
- 使用 `Semaphore` 限制并发数量
- 总是设置合理的超时时间
- 为异步操作编写单元测试（@pytest.mark.asyncio）
- 使用结构化并发（async with）

❌ **避免：**
- 混用同步和异步代码（会导致性能问题）
- 在异步函数中使用 `time.sleep()`，改用 `await asyncio.sleep()`
- 忘记 `await` 调用异步函数
- 在异步函数中执行 CPU 密集操作
- 创建过多的并发任务（无限制）

## 类型注解

```python
from typing import AsyncIterator, Awaitable

# 异步函数
async def fetch() -> dict:
    pass

# 异步生成器
async def stream() -> AsyncIterator[dict]:
    pass

# 可等待对象
future: Awaitable[dict] = fetch()

# 异步上下文管理器
async with connection as conn:
    pass
```

## 参考

- [asyncio 官方文档](https://docs.python.org/3/library/asyncio.html)
- 完整规范：`@/Python项目开发准则.md#10-异步编程规范`
