跟着AI学习C# Day26
📅 Day 26:C# 异步编程进阶
✅ 学习目标:
- 深入理解
async/await
的底层机制; - 掌握
ConfigureAwait(false)
的作用与使用场景; - 避免异步死锁,理解同步上下文(Synchronization Context);
- 掌握并行任务处理技巧(
Parallel
,PLINQ
); - 使用
ValueTask
替代Task
提升性能; - 构建高性能的异步 API;
- 编写一个结合多个异步优化技巧的示例程序(如高并发网络爬虫)。
🧠 一、回顾 async/await 基础知识
✅ async
和 await
是什么?
async
标记方法为异步方法;await
用于等待异步操作完成而不阻塞线程。
public async Task<string> DownloadAsStringAsync(string url)
{using var client = new HttpClient();return await client.GetStringAsync(url);
}
✅ 状态机原理简述:
编译器会将 async
方法转换为状态机对象(IAsyncStateMachine
),自动管理异步流程和上下文切换。
🔁 二、ConfigureAwait(false) 的意义与使用
✅ 默认行为(不加 ConfigureAwait(false)
)
在 UI 应用中(如 WPF、WinForms),默认会捕获当前的 SynchronizationContext
,以便 await
后继续回到 UI 线程执行后续代码。
❗️问题:可能导致死锁!
如果你在 UI 线程调用了 .Result
或 .Wait()
,而异步方法又试图回到 UI 线程,就会发生死锁。
✅ 正确做法:库方法应使用 ConfigureAwait(false)
public async Task<string> GetDataAsync()
{string result = await SomeNetworkCallAsync().ConfigureAwait(false);return Process(result);
}
⚠️ 在类库中始终使用
ConfigureAwait(false)
,除非你明确需要返回到特定上下文(如 UI)。
💀 三、避免异步死锁
❌ 错误写法(UI 线程中):
var result = GetDataAsync().Result;
这会导致主线程被阻塞,并且 await
后想回到这个线程,但该线程正等着结果,导致死锁。
✅ 正确写法:
var result = await GetDataAsync();
或者,在非 UI 场景中使用:
Task.Run(async () => await GetDataAsync()).Wait();
🧩 四、并行任务处理(Parallel & PLINQ)
✅ Parallel.For / Parallel.ForEach
适用于 CPU 密集型任务的并行执行。
Parallel.For(0, 10, i =>
{Console.WriteLine($"处理 {i},线程ID:{Thread.CurrentThread.ManagedThreadId}");
});
✅ PLINQ(Parallel LINQ)
并行查询,适合大数据集合处理:
var numbers = Enumerable.Range(1, 1000000);var result = numbers.AsParallel().Where(n => n % 3 == 0).Sum();Console.WriteLine("总和:" + result);
🧱 五、ValueTask vs Task(.NET Core 2.1+)
✅ Task<T>
的缺点:
每次调用都会分配内存(堆上创建对象),对高频调用或热路径有性能影响。
✅ ValueTask<T>
的优势:
- 如果结果已知(缓存命中、立即完成),则不分配;
- 适用于“大多数快速完成”的异步操作。
public ValueTask<int> GetCachedValueAsync()
{if (_cache.HasValue)return new ValueTask<int>(_cache.Value);elsereturn new ValueTask<int>(GetValueFromNetworkAsync());
}
✅ 注意:不能多次
await
,否则可能抛出异常。
🔄 六、自定义异步状态机(高级)
你可以通过实现 IValueTaskSource<TResult>
来构建自己的 ValueTask
实现,但这通常只在高性能框架开发中使用。
示例略(复杂度较高,需深入理解状态机机制)。
🧪 七、实战练习:高并发网页爬虫
功能要求:
- 并发下载多个网页;
- 使用
HttpClient
异步请求; - 避免死锁;
- 使用
ValueTask
缓存热门页面; - 支持配置最大并发数;
- 输出各页面大小。
示例代码框架:
class WebCrawler
{private readonly HttpClient _client = new();private readonly ConcurrentDictionary<string, string> _cache = new();public async Task<int> CrawlPageAsync(string url){// 使用缓存if (_cache.TryGetValue(url, out var cached))return cached.Length;// 下载网页string content = await _client.GetStringAsync(url).ConfigureAwait(false);_cache.TryAdd(url, content);return content.Length;}public async Task RunAsync(IEnumerable<string> urls){var tasks = urls.Select(url =>Task.Run(async () =>{int length = await CrawlPageAsync(url).ConfigureAwait(false);Console.WriteLine($"{url} 长度:{length}");}));await Task.WhenAll(tasks);}
}
📝 小结
今天你学会了:
ConfigureAwait(false)
的作用与使用时机;- 如何避免异步死锁;
- 使用
Parallel
和PLINQ
实现并行任务; ValueTask
的优势及其适用场景;- 自定义异步状态机的基本概念;
- 编写了一个高并发网页爬虫的异步优化示例。
掌握这些高级异步编程技巧,能显著提升应用程序的响应性、吞吐量和资源利用率,尤其在 Web API、微服务、桌面应用等场景中尤为重要。
🧩 下一步学习方向(Day 27)
明天我们将进入一个新的主题 —— C# 中的反射(Reflection)与元编程,你将学会:
- 如何在运行时动态加载类型、调用方法;
- 获取类成员信息(属性、方法、构造函数);
- 使用
System.Reflection.Emit
创建动态程序集; - 反射在依赖注入、序列化、ORM 等框架中的应用;
- 性能优化技巧(缓存反射结果、使用表达式树代替 Invoke);
- 编写一个基于反射的通用对象克隆器。