Task.Run适合CPU密集型工作(如计算、图像处理),避免阻塞UI;HTTP/文件等I/O操作应优先用原生async方法;不可在Task.Run中包裹await表达式或async方法而不await。
它适合把 CPU 密集型工作(比如大量计算、图像处理、JSON 解析)从主线程移出去,避免阻塞 UI 或响应。不是所有异步都该用 Task.Run——比如 HTTP 请求、文件读写,应优先用原生 async 方法(如 HttpClient.GetAsync、File.ReadAllTextAsync),它们底层不占线程。
Task.Run(() => ComputeHeavyWork()) 包裹纯计算逻辑,别包 await 表达式Task.Run 里调用另一个 async 方法却不 await,否则会得到一个未等待的 Task
Task.Run 可能增加线程池争用,反而降低吞吐量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 才能得到结果值声明了 async 方法却没 await 其返回的 Task,编译器不报错,但任务可能被丢弃、异常静默丢失、资源未释放——这就是常说的“fire-and-forget”陷阱。
DoSomethingAsync() 而不 await
_ = DoSomethingAsync(); 显式表明意图,并确保内部有 try/catch
Task,可能导致请求提前返回而后台任务仍在跑,引发状态不一致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,开销远低于新建线程或轮询Task.Delay 可能不可用,需改用协程或平台特定 API
await,而是判断哪个操作该用原生异步、哪个该扔给线程池、哪个根本不能丢弃——这些边界往往藏在业务逻辑深处,而不是语法里。