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

来源:这里教程网 时间:2026-02-21 17:39:13 作者:

Task.Run 适合什么场景

它适合把 CPU 密集型工作(比如大量计算、图像处理、JSON 解析)从主线程移出去,避免阻塞 UI 或响应。不是所有异步都该用

Task.Run
——比如 HTTP 请求、文件读写,应优先用原生
async
方法(如
HttpClient.GetAsync
File.ReadAllTextAsync
),它们底层不占线程。

Task.Run(() => ComputeHeavyWork())
包裹纯计算逻辑,别包
await
表达式
不要在
Task.Run
里调用另一个
async
方法却不
await
,否则会得到一个未等待的
Task<task></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<task></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
,而是判断哪个操作该用原生异步、哪个该扔给线程池、哪个根本不能丢弃——这些边界往往藏在业务逻辑深处,而不是语法里。

相关推荐