C# async /await 高频面试题(含答案 + 解析)
这是 .NET 开发必考题,我整理了最常问、最容易踩坑的面试题,覆盖基础、原理、线程、异常、性能、实战,直接背会就能通关。
一、基础概念题(必问)
- 什么是 C# 中的 async /await?
答案:async/await 是 C# 5.0 引入的异步编程语法糖,基于 Task 异步模型(TAP),用于以同步写法写异步代码,解决异步代码嵌套复杂、可读性差的问题。
async:标记方法为异步方法
await:等待异步操作完成,不阻塞线程
核心:它不是创建线程,而是线程复用 + IO 等待时释放线程。 - async 方法的返回值有哪几种?
答案(必考):
Task:无返回值的异步方法(对应 void)
Task:有返回值的异步方法
void:仅用于事件处理(如按钮点击),绝对不要在业务代码使用
ValueTask:高性能优化,减少内存分配
禁忌:业务方法禁止返回 void,无法捕获异常、无法等待、无法取消。 - await 做了什么?会不会阻塞线程?
答案:
遇到 await:挂起当前异步方法,立即把线程归还线程池
IO 完成后:恢复执行后续代码
不会阻塞线程!这是和 Wait()/Result 最大的区别
关键词:挂起方法 ≠ 阻塞线程。
二、线程与同步上下文(高频坑点) - async /await 会创建新线程吗?
答案:绝大多数情况不会!
IO 操作(网络、数据库、文件):不创建线程,纯内核等待
CPU 操作:用 Task.Run() 才会切到线程池线程
总结:async/await 是调度线程,不是创建线程。 - ConfigureAwait (false) 是什么?为什么要用?
答案:
await xxx.ConfigureAwait(false)
作用:不需要回到原同步上下文(UI 线程 /ASP.NET 请求上下文)
优点:减少开销、避免死锁、提升性能
使用场景:类库、通用方法必须加;UI 主线程不要用
三、死锁面试题(超级高频)
题目:下面代码为什么会死锁?怎么解决?
// 桌面/ASP.NET 环境
csharp
public void Test()
{
MethodAsync().Wait();
}
public async Task MethodAsync()
{
await Task.Delay(100);
}
答案:
原因:
Wait() 阻塞主线程
await 完成后想回到原主线程,但线程已被阻塞 → 死锁
解决方案:
csharp
await MethodAsync().ConfigureAwait(false);
或全程异步,不使用 Wait() / .Result。
四、异常捕获题
- async 方法中异常怎么捕获?
答案:
异步方法异常会封装到 Task 中
必须 await Task 才能捕获异常
用 try-catch 包裹 await
示例:
csharp
async Task Test()
{
try
{
await DoAsync();
}
catch (Exception ex)
{
// 捕获成功
}
}
- 多个异步异常怎么捕获?
答案:Task.WhenAll 会抛出 AggregateException,用 .InnerExceptions 遍历所有异常。
csharp
运行
csharp
try
{
await Task.WhenAll(t1, t2, t3);
}
catch (AggregateException ex)
{
var allEx = ex.InnerExceptions;
}
五、执行顺序题(必看)
题目:写出执行顺序
csharp
运行
Console.WriteLine(1);
Test();
Console.WriteLine(4);
async Task Test()
{
Console.WriteLine(2);
await Task.Delay(100);
Console.WriteLine(3);
}
答案:
1 → 2 → 4 → 3
解析:
async 方法开头同步执行
遇到 await 立即返回
后续代码是续行(Continuation),异步执行
六、实战与性能题
- async /await 优点?
答案:
代码扁平,无回调地狱
不阻塞线程,高并发下吞吐量更高
支持 try-catch,调试简单
基于 Task,可组合、可取消、可异常处理 - 异步和多线程的区别?
答案:
多线程:靠线程并行(占资源)
异步:IO 等待时释放线程,不占 CPU(高吞吐)
关系:异步可以用多线程,但不是必须 - 如何并发执行多个异步?
错误(串行):
csharp
await a();
await b();
await c();
正确(并发):
csharp
var t1 = a();
var t2 = b();
var t3 = c();
await Task.WhenAll(t1, t2, t3);
- Task.Run 和 async/await 的区别?
答案:
Task.Run:把同步方法丢到线程池,适合 CPU 密集
async/await:原生异步,适合 IO 密集(网络、数据库、文件)
七、高频简答题(背会直接用)
async void 为什么不能用?无法捕获异常、无法等待、无法取消、程序易崩溃。
await 和 Wait ()/Result 区别?await 不阻塞线程;Wait/Result 阻塞线程,会导致死锁。
异步方法的性能好在哪里?IO 时释放线程,能处理更多请求,提升服务吞吐量。
ValueTask 是什么?高性能结构体,减少 Task 堆分配,常用于高频调用的异步方法。
终极总结(面试必背)
1.async/await 是语法糖,基于 Task
2.await 不阻塞线程,只挂起方法
3.返回值优先 Task/Task,禁止 async void
4.类库必须用 ConfigureAwait (false)
5.不要用 Wait ()/Result,会死锁
6.并发用 WhenAll,串行用 await
7.IO 用异步,CPU 用 Task.Run