在.NET开发中,async/await这对关键字彻底改变了我们编写异步代码的方式。记得2012年刚接触这个特性时,我还在用复杂的回调地狱处理异步操作。现在让我们深入剖析这个让异步编程变得优雅的语法糖。
异步编程的核心目标是避免阻塞主线程。传统多线程方案需要手动管理线程池,而async/await通过状态机机制实现了更高效的协作式多任务。实际开发中,我们经常遇到这样的场景:从数据库读取数据时不想阻塞UI线程,或者需要并行处理多个网络请求。
重要提示:async/await不会创建新线程,它本质上是基于任务的 continuation 机制
当你编写一个async方法时,编译器会进行以下转换:
例如下面这个简单方法:
csharp复制async Task DemoAsync()
{
await Task.Delay(1000);
Console.WriteLine("Done");
}
会被编译为类似这样的结构:
csharp复制struct StateMachine : IAsyncStateMachine
{
public int state;
public AsyncTaskMethodBuilder builder;
public TaskAwaiter awaiter;
void MoveNext()
{
if (state == 0) {
awaiter = Task.Delay(1000).GetAwaiter();
if (!awaiter.IsCompleted) {
state = 1;
builder.AwaitOnCompleted(ref awaiter, ref this);
return;
}
}
awaiter.GetResult();
Console.WriteLine("Done");
}
}
状态机的典型生命周期:
每个await点都会成为状态机的一个检查点。我在实际项目中曾遇到一个bug:在循环中使用await时忘记考虑状态机重置,导致后续迭代跳过关键逻辑。
async/await会自动捕获和恢复执行上下文,包括:
在UI线程中,这个特性尤为重要:
csharp复制// 在UI线程调用
async void ButtonClick()
{
// 这里在UI线程
await Task.Delay(1000);
// 自动回到UI线程
textBox.Text = "Updated";
}
ConfigureAwait(false)可以避免上下文恢复,提升性能:
csharp复制async Task LoadDataAsync()
{
var data = await GetDataAsync().ConfigureAwait(false);
// 这里可能在线程池线程
Process(data);
}
但要注意:
async void的三大罪状:
正确做法:
csharp复制// 错误示范
async void BadMethod() { ... }
// 正确做法
async Task GoodMethod() { ... }
对于需要立即执行的任务:
csharp复制// 低效做法
var task = GetDataAsync();
await task;
// 高效做法
var task = GetDataAsync(); // 立即启动
// 这里可以并行做其他工作
await task;
正确处理取消请求:
csharp复制async Task LongOperationAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.Delay(1000, token);
// 定期检查取消状态
for (int i = 0; i < 100; i++) {
token.ThrowIfCancellationRequested();
await ProcessItemAsync(i, token);
}
}
我们可以创建自己的awaitable类型:
csharp复制public struct MyAwaiter : INotifyCompletion
{
public bool IsCompleted { get; }
public void OnCompleted(Action continuation) { ... }
public int GetResult() { ... }
}
public static MyAwaiter GetAwaiter(this MyType obj)
{
return new MyAwaiter();
}
C# 8.0引入的异步流:
csharp复制async IAsyncEnumerable<int> FetchData()
{
for (int i = 0; i < 10; i++) {
await Task.Delay(100);
yield return i;
}
}
await foreach (var item in FetchData())
{
Console.WriteLine(item);
}
处理异步环境下的资源竞争:
csharp复制private readonly SemaphoreSlim _lock = new(1);
async Task AccessResourceAsync()
{
await _lock.WaitAsync();
try {
// 临界区代码
} finally {
_lock.Release();
}
}
调试异步代码时,Visual Studio提供了:
我常用的诊断命令:
powershell复制!dumpasync -stacks // WinDbg中的异步堆栈分析
关键性能指标:
使用PerfView工具时重点关注:
典型死锁模式:
csharp复制async Task DeadlockDemo()
{
var task = GetDataAsync();
// 同步等待导致死锁
var result = task.Result;
}
解决方案:
由于构造函数不能是async的,推荐模式:
csharp复制class MyService
{
private MyService() { }
public static async Task<MyService> CreateAsync()
{
var instance = new MyService();
await instance.InitializeAsync();
return instance;
}
}
安全的事件触发模式:
csharp复制event Func<Task> MyEvent;
async Task RaiseEvent()
{
var handlers = MyEvent?.GetInvocationList();
if (handlers != null) {
foreach (Func<Task> handler in handlers) {
try {
await handler();
} catch (Exception ex) {
LogError(ex);
}
}
}
}
.NET线程池与任务调度的交互:
从.NET Core 2.1开始的重要改进:
ValueTask的适用情况:
示例:
csharp复制public ValueTask<int> GetCachedDataAsync()
{
if (_cache.TryGetValue(key, out var data))
return new ValueTask<int>(data);
return new ValueTask<int>(FetchFromNetworkAsync());
}
Unity的异步处理特点:
与传统ASP.NET的区别:
在我的基准测试中(i7-11800H, .NET 6):
| 场景 | 同步版本 | 异步版本 | 提升 |
|---|---|---|---|
| IO密集型 | 1200ms | 350ms | 3.4x |
| CPU密集型 | 850ms | 880ms | -3% |
| 混合型 | 1600ms | 600ms | 2.7x |
关键发现: