C#异步编程 - Task的用法总结

发布于:2025-09-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

在C#中,Task是异步编程的核心类型,它代表一个异步操作。它是对线程的更高级抽象,大大简化了异步和并行编程。

Task 状态

  • Created: 任务已通过构造函数创建,但尚未被安排(例如,通过Start()或TaskFactory.StartNew()方法安排执行)。
  • WaitingForActivation: 任务正在等待被安排到任务调度程序。这种状态通常用于由异步操作(如async/await)创建的任务,或者通过TaskCompletionSource创建的任务。
  • WaitingToRun: 任务已被安排执行,但尚未开始执行。它可能在等待线程可用。
  • Running: 正在运行,但尚未完成。
  • WaitingForChildrenToComplete:任务已经完成其本身的执行,并正在等待附加的子任务完成。只有当任务创建了子任务,并且父任务配置为等待子任务完成时,才会进入此状态。
  • RanToCompletion: 成功完成,没有抛出未处理的异常,也没有被取消。
  • Faulted: 因未处理的异常而完成。
  • Canceled: 任务已通过取消操作完成。这通常是由于CancellationToken被触发,任务通过抛出OperationCanceledException来响应取消请求。

创建和启动 Task

1.使用 Task.Run() (最常用)

用于将工作项排队到线程池,并返回一个表示该工作的 Task 或 Task。

// 用于无返回值的方法Task task = Task.Run(() =>{    Console.WriteLine("在线程池线程上执行的任务");    Thread.Sleep(1000);});task.Wait(); // 阻塞等待任务完成// 用于有返回值的方法Task<int> taskWithResult = Task.Run(() =>{    Console.WriteLine("计算中...");    return 42;});

2.使用 Task.Factory.StartNew()

比 Task.Run 提供更多选项(如任务创建选项、自定义调度器),但在大多数简单场景下,Task.Run 是首选。

ask task = Task.Factory.StartNew(() =>{    // 工作代码}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

3.使用 async/await 关键字 (现代方式)

async 方法会自动将代码包装成状态机,并返回一个 Task 或 Task。

public async Task<string> DownloadStringAsync(string url){    using (var httpClient = new HttpClient())    {        // await 会挂起当前方法,将控制权交回调用者,但不会阻塞线程。        string result = await httpClient.GetStringAsync(url);        // 当后台操作完成後,方法会在此处恢复执行。        return result;    }}// 调用var downloadTask = DownloadStringAsync("https://example.com");// ... 这里可以做其他事情 ...string content = await downloadTask; // 等待任务完成并获取结果

4.使用 Task.FromResult (用于同步返回结果)

当你有一个结果需要包装成 Task 以满足接口要求时非常有用,常用于模拟异步操作或缓存的值。

public Task<int> GetCachedNumberAsync(){    int cachedValue = 42; // 从缓存中同步获取    return Task.FromResult(cachedValue); // 无需启动新线程}

5.使用 Task.Delay (用于等待一段时间)

创建一个在指定时间后完成的任务,常用于异步等待。

ublic async Task ProcessAsync(){    await Task.Delay(2000); // 异步等待2秒,不阻塞线程    Console.WriteLine("2秒后执行");}

等待和获取结果

1.await 运算符 (推荐)

  • 非阻塞地等待任务完成。它会将方法后续代码包装为任务的“延续”,并在任务完成后执行。
  • 只能在 async 方法中使用。
sync Task MyMethod(){    await SomeAsyncOperation();    Console.WriteLine("SomeAsyncOperation 完成后才执行");}

2.Task.Result 和 Task.Wait() (不推荐,容易死锁)

  • 这两个属性/方法会阻塞当前线程,直到任务完成。
  • 在 UI 线程或 ASP.NET 的请求上下文中调用时,极易引起死锁。
  • 黄金法则: 在异步代码中,尽量使用 await,避免使用 .Result 或 .Wait()。
/ 危险代码(在UI线程中调用会导致死锁)var result = GetAsyncResult().Result;GetAsyncResult().Wait();

3.Task.WaitAll() 和 Task.WaitAny() (阻塞式等待多个任务)

阻塞当前线程,等待提供的所有任务或任一任务完成。

ask task1 = Task.Delay(1000);Task task2 = Task.Delay(2000);Task.WaitAll(task1, task2); // 阻塞,直到两个任务都完成Console.WriteLine("Both done!");int firstFinishedIndex = Task.WaitAny(task1, task2); // 阻塞,直到任一任务完成Console.WriteLine($"Task {firstFinishedIndex} finished first.");

4.Task.WhenAll() 和 Task.WhenAny() (非阻塞式等待多个任务)

返回一个新的 Task,该任务会在所有提供任务或任一任务完成时完成。通常与 await 一起使用。

async Task ProcessMultipleTasks(){    Task<int> task1 = FetchData1Async();    Task<string> task2 = FetchData2Async();    // 非阻塞地等待所有任务完成    var results = await Task.WhenAll(task1, task2);    int data1 = results[0];    string data2 = results[1];    // 或者,非阻塞地等待任一任务完成    Task firstFinishedTask = await Task.WhenAny(task1, task2);    if (firstFinishedTask == task1)    {        Console.WriteLine("FetchData1Async 先完成");    }}

异常处理

Task 中的异常会被捕获并包装在 AggregateException 中。使用 await 时,它会自动解包 AggregateException,抛出最初的异常,使处理更自然。

// 1. 使用 try-catch 与 await (推荐)try{    await SomeOperationThatMightFailAsync();}catch (InvalidOperationException ex){    // 直接捕获具体的异常    Console.WriteLine(ex.Message);}// 2. 处理未使用 await 的任务的异常Task faultyTask = Task.Run(() => throw new Exception("Oops!"));try{    faultyTask.Wait(); // 或者访问 .Result}catch (AggregateException ae){    // 必须处理 AggregateException    foreach (var e in ae.InnerExceptions)    {        Console.WriteLine(e.Message);    }}// 3. 检查任务的 Exception 属性if (faultyTask.IsFaulted){    foreach (var e in faultyTask.Exception.InnerExceptions)    {        Console.WriteLine(e.Message);    }}

取消操作

使用 CancellationTokenSource 和 CancellationToken 来协作式地取消任务。

public async Task DoLongRunningWorkAsync(CancellationToken cancellationToken = default){    for (int i = 0; i < 100; i++)    {        // 检查是否请求了取消        cancellationToken.ThrowIfCancellationRequested();        // 或者轮询检查        // if (cancellationToken.IsCancellationRequested)        // {        //     // 进行清理工作,然后...        //     throw new OperationCanceledException(cancellationToken);        // }        await Task.Delay(1000, cancellationToken); // Delay 也支持取消        Console.WriteLine($"Working... {i}");    }}// 调用方var cts = new CancellationTokenSource();try{    // 在5秒后自动取消    cts.CancelAfter(5000);    await DoLongRunningWorkAsync(cts.Token);}catch (OperationCanceledException){    Console.WriteLine("任务被取消了。");}finally{    cts.Dispose();}

任务延续

ContinueWith 创建一个会在指定任务完成后立即执行的延续任务,无论前驱任务是成功完成、失败还是被取消。

Task initialTask = Task.Run(() => Console.WriteLine("初始任务"));
// 当前一个任务完成后执行延续任务Task continuationTask = initialTask.ContinueWith(previousTask =>{    Console.WriteLine("前一个任务已完成");});
// 带条件的延续initialTask.ContinueWith(t =>{    Console.WriteLine("只在任务失败时执行");}, TaskContinuationOptions.OnlyOnFaulted);
initialTask.ContinueWith(t =>{    Console.WriteLine("只在任务成功时执行");}, TaskContinuationOptions.OnlyOnRanToCompletion);
initialTask.ContinueWith(t =>{    Console.WriteLine("只在任务被取消时执行");}, TaskContinuationOptions.OnlyOnCanceled);


网站公告

今日签到

点亮在社区的每一天
去签到