Task.Run适合CPU密集型工作(如计算、图像处理),避免阻塞UI;HTTP/文件等I/O操作应优先用原生async方法;不可在Task.Run中包裹await表达式或async方法而不await。

Task.Run 适合什么场景
它适合把 CPU 密集型工作(比如大量计算、图像处理、JSON 解析)从主线程移出去,避免阻塞 UI 或响应。不是所有异步都该用 Task.Run——比如 HTTP 请求、文件读写,应优先用原生 async 方法(如 HttpClient.GetAsync、File.ReadAllTextAsync),它们底层不占线程。
- 用
Task.Run(() => ComputeHeavyWork())包裹纯计算逻辑,别包await表达式 - 不要在
Task.Run里调用另一个async方法却不await,否则会得到一个未等待的Task - ASP.NET Core 中过度使用
Task.Run可能增加线程池争用,反而降低吞吐量
await Task.WhenAll 和 await Task.WhenAny 的区别
Task.WhenAll 等待所有任务完成才继续;Task.WhenAny 一有任一任务完成(成功/异常/取消)就返回,常用于超时控制或竞速请求。
var tasks = new[] {
DownloadAsync("url1"),
DownloadAsync("url2"),
DownloadAsync("url3")
};
// 等全部完成
var results = await Task.WhenAll(tasks);
// 只等第一个完成(比如取最快响应)
var first = await Task.WhenAny(tasks);
var result = await first;
-
Task.WhenAll抛出异常时,会聚合所有子任务的异常(AggregateException),需遍历InnerExceptions -
Task.WhenAny返回的是Task,必须再await才能得到结果值 - 两者都不改变原任务的执行状态,只是观察行为
忘记 await Task 导致的“火球”问题
声明了 async 方法却没 await 其返回的 Task,编译器不报错,但任务可能被丢弃、异常静默丢失、资源未释放——这就是常说的“fire-and-forget”陷阱。
- 除非明确设计为后台任务(且已加异常捕获和日志),否则不要直接调用
DoSomethingAsync()而不await - 若真要忽略结果,至少用
_ = DoSomethingAsync();显式表明意图,并确保内部有try/catch - 在 ASP.NET Core 控制器中直接丢弃
Task,可能导致请求提前返回而后台任务仍在跑,引发状态不一致
Task.Delay 不是 Thread.Sleep 的异步替代品
Task.Delay 是非阻塞的计时器,不占用线程;Thread.Sleep 是同步阻塞,会卡死当前线程。在 async 方法中误用 Thread.Sleep,等于把异步代码写成了伪异步。
public async Task DoWorkAsync()
{
// ✅ 正确:不占线程,可被调度器挂起
await Task.Delay(1000);
// ❌ 错误:同步阻塞,整个 async 流程卡住 1 秒
Thread.Sleep(1000);
}
-
Task.Delay支持CancellationToken,可用于实现可取消的等待 - 它底层基于
System.Threading.Timer,开销远低于新建线程或轮询 - 在 Unity 或某些受限环境里,
Task.Delay可能不可用,需改用协程或平台特定 API
await,而是判断哪个操作该用原生异步、哪个该扔给线程池、哪个根本不能丢弃——这些边界往往藏在业务逻辑深处,而不是语法里。









