在 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# 开发者最容易犯的错
-
**忘记
asyncio.run()**:Python 需要手动启动事件循环 -
**在非异步函数中
await**:必须在async def函数中 -
用同步库 :
requests是同步的,要用aiohttp -
阻塞事件循环:同步操作会阻塞整个事件循环
-
**忘记
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 章示例)
💬 欢迎点赞、收藏、转发,你的支持是我持续创作的动力!