.Net Core 中的 async 和 await 的原理
- async 关键字用于标记异步方法,await 用于异步调用。
- 底层是基于 Task 和 Task
,依赖于 状态机(State Machine),核心是 SynchronizationContext 和 TaskScheduler。 - await 遇到 Task 时,会先 挂起当前方法,然后继续执行其他任务,待任务完成后再回到原来的执行位置。
第一种写法
static async Task Main(){Console.WriteLine("1");Task<int> task = GetDataAsync();Console.WriteLine("2...");int result = await task;Console.WriteLine($"3. result:{result}");Console.WriteLine("4. end");}static async Task<int> GetDataAsync(){Console.WriteLine(" >> [async] strat");await Task.Delay(10000); Console.WriteLine(" >> [saync] end");return 42;}
}
执行流程
1>> [async] strat (异步任务开始,但未等待)
2... (继续执行,不等待异步任务)>> [async] end (10秒后异步任务完成)
3. result:42 (await 处阻塞,等待异步任务完成)
4. end
关键点
- GetDataAsync() 在 Task
task = GetDataAsync(); 处启动,但不会阻塞 Main 方法。 - Console.WriteLine("2..."); 不会等待 GetDataAsync() 结束,而是继续执行。
- await task; 此时才真正等待异步任务完成。
第二种写法
static async Task Main(){Console.WriteLine("1");var result = await GetDataAsync();Console.WriteLine("2...");Console.WriteLine($"3. result:{result}");Console.WriteLine("4. end");}static async Task<int> GetDataAsync(){Console.WriteLine(" >> [async] strat");await Task.Delay(10000); Console.WriteLine(" >> [saync] end");return 42;}
}
执行流程:
1>> [async] strat (异步任务开始)>> [async] end (10秒后异步任务完成)
2... (异步任务结束后,继续执行)
3. result:42
4. end
关键点
- await GetDataAsync(); 直接等待 GetDataAsync() 结束后才执行后续代码。
- 代码执行顺序是严格的顺序执行,不会提前执行 Console.WriteLine("2...")。
区别总结
第一种写法 (Task<int> task = GetDataAsync(); ) |
第二种写法 (await GetDataAsync(); ) |
|
---|---|---|
异步任务启动时机 | GetDataAsync() 立即启动,但不会阻塞 |
GetDataAsync() 立即启动,但 await 直接阻塞后续代码 |
是否可以并行执行代码 | 可以,Console.WriteLine("2..."); 不会等待 |
不可以,必须等 GetDataAsync() 完成后执行下一行 |
适合的场景 | 适用于需要异步任务执行的同时继续其他工作 | 适用于必须等任务完成后才能继续 |
await GetDataAsync().ConfigureAwait(false)
- 这行代码是真正的异步执行,它不会阻塞当前线程,而是允许线程去执行其他任务,直到 GetUtteranceInfo 任务完成。
- await 关键字会等待 GetUtteranceInfo 执行完成并返回结果,但不会阻塞线程。
- ConfigureAwait(false) 作用是避免恢复到原来的同步上下文,在 ASP.NET 或 UI 应用程序(如 WPF/WinForms)中,这有助于提高性能并减少死锁风险。
何时使用 ConfigureAwait(false)
场景 | 是否使用 ConfigureAwait(false) |
说明 |
---|---|---|
库代码(Class Library, SDK) | ✅ 推荐 | 避免影响调用方的上下文,提高兼容性 |
ASP.NET Core / MVC / Web API | ✅ 推荐 | 避免上下文切换,提高性能 |
后台任务(Background Service) | ✅ 推荐 | 不需要回到主线程,减少线程切换 |
Console 应用(.NET Core) | ✅ 推荐 | 避免死锁 |
WPF / WinForms UI 操作 | 🚫 不要使用 | 需要回到 UI 线程,否则 UI 可能无法更新 |
简而言之:
后台代码、库代码、ASP.NET 后端 → ConfigureAwait(false)
UI 操作 → 不要用 ConfigureAwait(false),必要时手动切回 UI 线程
示例(WPF/WinForms UI 操作 - 不要用 ConfigureAwait(false))
public async Task LoadData()
{var data = await FetchDataAsync().ConfigureAwait(false); // ❌ UI 线程丢失myLabel.Text = data; // ⚠️ 这里会抛出异常,因为 UI 线程已丢失
}
正确做法
public async Task LoadData()
{var data = await FetchDataAsync().ConfigureAwait(false); await Dispatcher.InvokeAsync(() => myLabel.Text = data); // ✅ 切回 UI 线程
}
GetDataAsync().GetAwaiter().GetResult()
- 在非异步方法中使用。
- 这行代码会阻塞当前线程,直到 GetUtteranceInfo 任务完成,并返回结果。
- GetAwaiter().GetResult() 是 Task 的一种同步等待方式,它会获取 Task 的结果,但如果 Task 抛出异常,它会将异常直接传播,而不会进行 AggregateException 封装。
- 可能会导致死锁:如果当前代码运行在同步上下文(如 ASP.NET 的 UI 线程),调用 GetResult() 可能会阻塞线程并导致死锁。