C#转Python第5.1篇:异步编程:asyncio vs async/await,语法相似但机制不同

在 C# 里写异步代码,用 async/await------语法简洁,编译器帮你处理状态机,Task 模型成熟稳定。

在 Python 里写异步代码,用 asyncio + async/await------语法几乎一样,但底层机制完全不同,还得处理事件循环。

当我第一次写 Python 的 async def 的时候,我以为可以像 C# 那样直接 await 一个 HTTP 请求。然后...报错了------await 只能在 async 函数里用,而且得先运行事件循环。

Python 的 asyncio 需要手动管理事件循环,而 C# 的 async/await 由编译器自动处理状态机------两种异步模型各有取舍,下面逐一拆解。

基础语法对比

C# 版本:

复制代码
// 异步方法
public async Task<string> GetDataAsync(string url)
{
    using var client = new HttpClient();
    string result = await client.GetStringAsync(url);
    return result;
}

// 调用异步方法
public async Task Main()
{
    string data = await GetDataAsync("https://api.example.com");
    Console.WriteLine(data);
}

Python 版本:

复制代码
import asyncio
import aiohttp

# 异步方法
async def get_data(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

# 调用异步方法
async def main():
    data = await get_data("https://api.example.com")
    print(data)

# 需要手动运行事件循环
asyncio.run(main())

对比一下:

特性 C# Python
定义异步方法 async Task<T> async def
等待 await await
返回类型 Task<T> 自动推断
运行方式 直接调用 asyncio.run()
事件循环 编译器处理 手动管理

语法几乎一样,但 C# 的 async/await 是编译器魔法 ------编译器自动生成状态机;Python 的 async/await 是协程------需要事件循环来调度。

Task vs Coroutine

C# 的 Task 是线程池任务------可以并发执行,底层是线程池:

复制代码
// 并发执行多个 Task
var task1 = GetDataAsync("url1");
var task2 = GetDataAsync("url2");
var task3 = GetDataAsync("url3");

// 等待所有完成
await Task.WhenAll(task1, task2, task3);

// 或者等待任意一个完成
var first = await Task.WhenAny(task1, task2, task3);

Python 的 Coroutine 是协程------在单线程中交替执行,需要事件循环调度:

复制代码
# 并发执行多个协程
async def main():
    task1 = asyncio.create_task(get_data("url1"))
    task2 = asyncio.create_task(get_data("url2"))
    task3 = asyncio.create_task(get_data("url3"))

    # 等待所有完成
    await asyncio.gather(task1, task2, task3)

asyncio.run(main())

关键区别:

特性 C# Task Python Coroutine
并发模型 多线程(线程池) 单线程(事件循环)
创建方式 Task.Run() 或 async 方法 asyncio.create_task()
等待方式 await 或 Task.WhenAll await 或 asyncio.gather
取消 CancellationToken asyncio.CancelledError
异常处理 try-catch try-except

C# 的 Task 底层是线程池,可以真正并行;Python 的协程是单线程并发,遇到 I/O 操作时切换到其他协程。

实际场景:并发 HTTP 请求

C# 版本:

复制代码
public async Task<List<string>> FetchAllAsync(IEnumerable<string> urls)
{
    var tasks = urls.Select(url => FetchOneAsync(url));
    var results = await Task.WhenAll(tasks);
    return results.ToList();
}

private async Task<string> FetchOneAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

// 使用
var urls = new[] { "url1", "url2", "url3" };
var results = await FetchAllAsync(urls);

Python 版本:

复制代码
import asyncio
import aiohttp

async def fetch_one(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def fetch_all(urls):
    tasks = [fetch_one(url) for url in urls]
    return await asyncio.gather(*tasks)

# 使用
async def main():
    urls = ["url1", "url2", "url3"]
    results = await fetch_all(urls)

asyncio.run(main())

C# 的 Task.WhenAll 和 Python 的 asyncio.gather 功能几乎一样------都是等待所有任务完成。

异步迭代器

C# 版本:

复制代码
// 异步迭代器(C# 8+)
public async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(100);  // 模拟异步操作
        yield return i;
    }
}

// 使用
await foreach (var num in GenerateNumbersAsync())
{
    Console.WriteLine(num);
}

Python 版本:

复制代码
# 异步迭代器
async def generate_numbers():
    for i in range(10):
        await asyncio.sleep(0.1)  # 模拟异步操作
        yield i

# 使用
async def main():
    async for num in generate_numbers():
        print(num)

asyncio.run(main())

C# 用 IAsyncEnumerable<T> 和 await foreach,Python 用 async for------语法几乎一样。

异步上下文管理器

C# 版本:

复制代码
// 异步资源管理
public class AsyncResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await Task.Delay(100);  // 异步清理
        Console.WriteLine("资源已释放");
    }
}

// 使用
await using var resource = new AsyncResource();
// 使用资源

Python 版本:

复制代码
# 异步上下文管理器
class AsyncResource:
    async def __aenter__(self):
        await asyncio.sleep(0.1)  # 异步初始化
        print("资源已获取")
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await asyncio.sleep(0.1)  # 异步清理
        print("资源已释放")

# 使用
async def main():
    async with AsyncResource() as resource:
        pass  # 使用资源

asyncio.run(main())

C# 用 IAsyncDisposable 和 await using,Python 用 __aenter__/__aexit__ 和 async with------设计哲学一样,都是异步资源管理。

设计哲学

C# 的异步是"编译器魔法"------编译器自动生成状态机,开发者只需要写 async/await,底层细节全隐藏。

Python 的异步是"显式事件循环"------需要手动管理事件循环,理解协程调度机制,更底层但更灵活。

C# 的异步像是"自动挡"------你只管踩油门,换挡是编译器的事; Python 的异步像是"手动挡"------你得自己换挡,但能更精确地控制。

更深层的原因:

  • C# 的异步基于线程池,可以利用多核 CPU

  • Python 的异步基于单线程事件循环,受 GIL 限制,适合 I/O 密集型任务

迁移指南:C# 开发者最容易犯的错

  1. **忘记 asyncio.run()**:Python 需要手动启动事件循环

  2. **在非异步函数中 await**:必须在 async def 函数中

  3. 用同步库 :requests 是同步的,要用 aiohttp

  4. 阻塞事件循环:同步操作会阻塞整个事件循环

  5. **忘记 async with**:异步资源要用异步上下文管理器

坑点提醒

同步操作阻塞事件循环------千万别在异步代码里用同步操作:

复制代码
import asyncio
import requests  # 同步库!

async def bad_example():
    # 这会阻塞整个事件循环!
    response = requests.get("https://api.example.com")
    return response.text

# 正确做法:用异步库
async def good_example():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com") as response:
            return await response.text()

**忘记 await**------调用异步函数必须 await:

复制代码
async def get_data():
    return "data"

# 错误!
result = get_data()  # 返回协程,不是结果

# 正确
result = await get_data()

事件循环只能运行一次 ------不能嵌套 asyncio.run():

复制代码
async def main():
    await asyncio.run(other())  # 错误!不能嵌套

# 正确做法:用 asyncio.gather
async def main():
    await asyncio.gather(main1(), main2())

Python 3.14 新特性

TaskGroup(Python 3.11+,3.14 更成熟)

Python 3.11 引入的 asyncio.TaskGroup 提供了更优雅的并发任务管理:

复制代码
import asyncio

async def fetch_data(url):
    await asyncio.sleep(1)
    return f"Data from {url}"

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch_data("url1"))
        task2 = tg.create_task(fetch_data("url2"))
        task3 = tg.create_task(fetch_data("url3"))
    
    # 所有任务完成后才到这里
    print(task1.result(), task2.result(), task3.result())

asyncio.run(main())

对比 C# 的 Task.WhenAll:

特性 C# Python 3.14
等待所有任务 Task.WhenAll() asyncio.gather() 或 TaskGroup
异常处理 任一任务失败立即抛出 TaskGroup 自动取消其他任务
取消 CancellationToken TaskGroup 自动取消

asyncio 内省能力(Python 3.14 新增)

Python 3.14 新增了异步任务的内省工具,可以查看正在运行的任务:

复制代码
# 查看进程中的异步任务
python -m asyncio ps PID

# 查看异步调用树
python -m asyncio pstree PID

这对于调试长时间运行的异步程序非常有用。

一句话总结

C# 的 async/await 是"自动挡",Python 的 asyncio 是"手动挡"------语法相似,但机制完全不同。


📦 示例代码:C# 转 Python 全系列配套练习代码(含 48 章示例)

💬 欢迎点赞、收藏、转发,你的支持是我持续创作的动力!

相关推荐
yi01144 分钟前
DAY27: LeetCode 110. 平衡二叉树 | 在递归中判断是否平衡
人工智能·笔记·python·算法·leetcode·二叉树·递归
LMT_Lanmantech1 小时前
MATLAB合规管理:如何避免侵权赔偿和审计风险
开发语言·matlab·信息可视化
阿华田5121 小时前
Java 类加载冲突实战:解决 is not an instance of
c#·linq
言乐61 小时前
PythonLOLM自动生成复盘报告模型
开发语言·前端·数据库·人工智能·python
霸道流氓气质1 小时前
HashiCorp Vault 与云 KMS 入门实战:动态密钥、SecretLeaseContainer 与 Java 集成完整指南
java·开发语言
水水不水啊1 小时前
告别切换N个窗口调试:我用 Python + WebView 做了款现代化串口助手(含 TCP/UDP、波形、协议解析)
python·tcp/ip·udp
论文复现现场1 小时前
DeepSeek V4.1 Flash 各平台价格怎么比?缓存、峰谷计费与 Python 成本计算
开发语言·python·缓存·大模型·成本优化·deepseek
xuxigifxfh1 小时前
牛客:HJ21 简单密码
java·开发语言·算法
打工仔折腾 AI2 小时前
鸿蒙平板远控电脑实测:ToDesk蓝牙键鼠、双模式鼠标与跨端剪贴板落地记录
人工智能·后端·python·性能优化·计算机外设·电脑·harmonyos