在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){// 必须处理 AggregateExceptionforeach (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);