async/await 本身不创建新线程,仅通过状态机挂起方法并注册延续;线程切换取决于上下文捕获(如 configureawait)和执行环境,i/o 异步不占线程,cpu 密集型操作须用 task.run。
async/await 本身不创建新线程
绝大多数情况下,async/await 不会主动创建新线程。它依赖于当前 SynchronizationContext 或 TaskScheduler 来决定后续代码在哪执行——比如 UI 线程(WinForms/WPF)或 ASP.NET Core 的请求上下文,都不是新线程。
常见误解是“await 就等于后台线程”,其实只有显式调用 Task.Run()、Task.Factory.StartNew() 或 I/O 操作底层触发的线程池回调,才可能用到线程池线程(但那也不是 await 创建的)。
-
await只是把方法拆成状态机,挂起当前逻辑,注册一个延续(continuation) - 挂起后控制权立刻交还给调用方,不阻塞当前线程
- I/O 完成时,.NET 通过 I/O Completion Port(IOCP)通知线程池取一个空闲线程来执行 continuation,这个线程可能是原线程,也可能是线程池里的任意一个
线程切换发生在 await 后续代码(continuation)的调度时刻
是否发生线程切换,取决于 await 后面那部分代码(即 await 之后的语句)被调度到哪个上下文执行。关键看两点:
- 有没有捕获当前上下文(默认会,除非用了
.ConfigureAwait(false)) - 当前上下文是否支持同步调度(如 UI 线程有
SynchronizationContext,ASP.NET Core 6+ 默认没有)
例如在 WinForms 中: