3.1 Task vs Thread:本质区别
Task是“操作”(Operation),Thread是“执行单元”(Execution Unit)。Task可运行在线程池线程、主线程或无线程(同步执行)上:
〈示例〉:Task.Run与Thread的区别
// 使用Task(推荐):自动使用线程池
var task = Task.Run(() => {
Console.WriteLine($"Task线程ID: {Thread.CurrentThread.ManagedThreadId}");
});
// 手动创建Thread(不推荐):独占线程
var thread = new Thread(() => {
Console.WriteLine($"Thread线程ID: {Thread.CurrentThread.ManagedThreadId}");
});
thread.Start();
💡 关键点:Task.Run()默认使用ThreadPool,而Thread始终创建新线程。优先使用Task.Run()而非Thread.Start()。
3.2 async/await的编译器魔法
async/await并非创建新线程,而是状态机(StateMachine)实现的语法糖。其核心机制:
- 编译器生成状态机类,将方法拆分为多个状态
- await等待的Task完成时,通过SynchronizationContext恢复上下文
- UI应用中,恢复到UI线程;控制台中,可能在任意线程继续
〈反例〉:导致死锁的async/await用法
// UI线程中调用
private async void Button_Click(object sender, EventArgs e) {
var result = await DownloadDataAsync(); // await后需回到UI线程
}
// DownloadDataAsync内部使用.Result阻塞(错误!)
public async Task DownloadDataAsync() {
var client = new HttpClient();
return await client.GetStringAsync("https://example.com");
}
当DownloadDataAsync内部使用.Result时,UI线程被阻塞等待,而await需UI线程恢复——形成死锁。
3.3 异步I/O与线程释放
真正的异步I/O(如HttpClient.GetStringAsync)不占用线程池线程!其底层使用操作系统异步API(如IOCP),仅在I/O完成时短暂占用线程处理回调:
〈对比〉:同步 vs 异步I/O线程占用
// 同步I/O:线程被阻塞
var data1 = File.ReadAllText("large.txt"); // 线程挂起直到完成
// 异步I/O:线程被释放回线程池
var data2 = await File.ReadAllTextAsync("large.txt"); // 线程可处理其他任务
💡 最佳实践:所有I/O操作(数据库、网络、文件)必须使用Async版本(ReadAsStringAsync, ReadAsync),避免阻塞线程池。