C# Task使用方法 C#如何使用Task进行异步操作

12次阅读

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

C# Task使用方法 C#如何使用Task进行异步操作

Task.Run 适合什么场景

它适合把 CPU 密集型工作(比如大量计算、图像处理、jsON 解析)从线程移出去,避免阻塞 UI 或响应。不是所有异步都该用 Task.Run——比如 HTTP 请求、文件读写,应优先用原生 async 方法(如 HttpClient.GetAsyncFile.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,而是判断哪个操作该用原生异步、哪个该扔给线程池、哪个根本不能丢弃——这些边界往往藏在业务逻辑深处,而不是语法里。

text=ZqhQzanResources