async/await 不开启新线程,而是让IO操作不阻塞主线程;必须返回Task或Task<T>,禁用async void;await仅限async方法内且只能作用于可等待对象;避免.Result/.Wait()防死锁;并发需用Task.WhenAll或并行启动任务。

async 和 await 不是用来“开启新线程”的,而是让阻塞点(比如网络请求、文件读取)不卡主线程——你写的是顺序逻辑,运行时自动交出控制权,等 IO 完了再回来。
async 方法必须返回 Task 或 Task<T>,别用 void
你写 async void,编译器不会报错,但这是危险操作:
- 调用方无法
await它,也没法用.Wait()或.Result等待完成 - 异常会直接炸到 SynchronizationContext(比如 UI 线程),可能静默丢失
- 单元测试几乎没法断言它的行为
正确姿势是:
public async Task DoWorkAsync()
{
await Task.Delay(1000);
}
<p>public async Task<int> GetValueAsync()
{
await Task.Delay(500);
return 42;
}入口点 Main 方法从 C# 7.1 起支持 async Task Main(),别再套一层 Task.Run(...).Wait()。
await 只能出现在 async 方法里,且只能等“可等待对象”
await 不是万能胶,它只认实现了 GetAwaiter() 并返回 INotifyCompletion 的类型。常见合法目标有:
-
Task、Task<T> -
ValueTask、ValueTask<T>(避免小任务堆分配) TaskCompletionSource<T>.Task- C# 8+ 的
IAsyncEnumerable<T>(配合await foreach)
下面这些写法都会编译失败:
public void BadMethod()
{
await Task.Delay(100); // ❌ 编译错误:不在 async 方法中
}
<p>public async Task AlsoBad()
{
await "hello"; // ❌ 编译错误:"string" 不可等待
await DateTime.Now; // ❌ 同上
}别在 async 方法里调用 .Result 或 .Wait()
这是最常见的死锁来源,尤其在 WinForms / WPF / ASP.NET(旧版)中:
-
.Result和.Wait()会同步阻塞当前线程,等待 Task 完成 - 而很多异步方法(如
HttpClient.GetStringAsync)默认要回到原始上下文(比如 UI 线程)才能继续执行 - 结果就是:UI 线程被
.Wait()卡住 → 异步回调回不来 → 死锁
解决办法只有一条:全程 async/await 向上穿透。如果真要同步等(极少场景),用 ConfigureAwait(false) 避免上下文捕获:
public async Task<string> GetDataAsync()
{
var client = new HttpClient();
// 下面这行加 ConfigureAwait(false) 是为了告诉编译器:
// “完成后不用非得回到 UI 线程,随便哪个线程池线程都行”
return await client.GetStringAsync("https://api.example.com").ConfigureAwait(false);
}async 不等于并行,多个 await 默认仍是串行
写成这样,是依次等每个请求完成:
var a = await GetAAsync(); var b = await GetBAsync(); // 要等 a 完了才开始 var c = await GetCAsync(); // 要等 b 完了才开始
想真正并发发请求,得先“发出去”,再统一 await:
var taskA = GetAAsync(); var taskB = GetBAsync(); var taskC = GetCAsync(); <p>var a = await taskA; var b = await taskB; var c = await taskC;
或者更简洁地用 Task.WhenAll:
var (a, b, c) = await Task.WhenAll(GetAAsync(), GetBAsync(), GetCAsync());
注意:WhenAll 任一失败,整个就失败;需要容错请手动 await 每个 task 并 try/catch。
最常被忽略的一点:async/await 的价值不在“快”,而在“不卡”。IO 密集型场景下它省的是线程,不是时间。如果你把 CPU 密集型操作(比如大数组排序)包进 Task.Run 再 await,反而因上下文切换和调度开销变得更慢。



















