C#中的Task是.NET框架提供的用于表示异步操作的类型,属于Task Parallel Library(TPL)的核心组件,它封装了异步执行的代码逻辑,相比传统的Thread类型,Task提供了更丰富的API和更便捷的任务管理、结果返回、异常处理能力,是C#中实现异步编程的首选方案。

Task的基础概念与创建方式
Task本质是对异步操作的抽象,它可以表示一个正在执行或者将要执行的操作,支持返回结果和不返回结果两种形式,分别对应Task和Task<TResult>类型。常见的Task创建方式有以下几种:
1. 使用Task.Run创建并启动Task
Task.Run是最常用的创建并启动Task的方式,它会将任务提交到线程池执行,示例代码如下:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 创建不返回结果的Task
Task task1 = Task.Run(() =>
{
Console.WriteLine("Task1开始执行");
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Task1执行完成");
});
// 创建返回结果的Task
Task<int> task2 = Task.Run(() =>
{
Console.WriteLine("Task2开始执行,计算1到10的和");
int sum = 0;
for (int i = 1; i <= 10; i++)
{
sum += i;
}
return sum;
});
// 等待task2执行完成并获取结果
task2.Wait();
Console.WriteLine($"Task2的计算结果为:{task2.Result}");
// 等待task1执行完成
task1.Wait();
Console.WriteLine("所有任务执行完成");
}
}
2. 使用Task.Factory.StartNew创建Task
Task.Factory.StartNew提供了更多的任务创建配置选项,比如可以指定任务的调度方式、是否长时间运行等,示例代码如下:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 创建长时间运行的任务,避免占用线程池线程
Task longTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("长时间运行的任务开始执行");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("长时间运行的任务执行完成");
}, TaskCreationOptions.LongRunning);
longTask.Wait();
Console.WriteLine("长时间任务执行结束");
}
}
Task的常用操作
任务等待与延续
Task提供了多种等待和延续的方式,用于处理任务执行完成后的逻辑:
Wait():阻塞当前线程,直到Task执行完成WaitAll(Task[] tasks):阻塞当前线程,直到所有传入的Task都执行完成WaitAny(Task[] tasks):阻塞当前线程,直到任意一个传入的Task执行完成ContinueWith(Action<Task> continuationAction):在Task执行完成后,启动一个新的Task执行延续逻辑,不会阻塞当前线程
ContinueWith的使用示例如下:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
Console.WriteLine("主任务开始执行");
System.Threading.Thread.Sleep(1000);
Console.WriteLine("主任务执行完成");
}).ContinueWith((t) =>
{
Console.WriteLine("主任务执行完成后的延续任务开始执行");
System.Threading.Thread.Sleep(500);
Console.WriteLine("延续任务执行完成");
});
// 避免主线程退出导致任务无法执行完成
System.Threading.Thread.Sleep(2000);
}
}
异常处理
Task中抛出的异常会被捕获并存储在Task.Exception属性中,不会直接抛出到当前线程。如果任务有异常且没有被观察,在任务被垃圾回收时可能会触发TaskScheduler.UnobservedTaskException事件。处理Task异常的示例如下:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Task task = Task.Run(() =>
{
Console.WriteLine("任务开始执行");
throw new InvalidOperationException("任务执行过程中发生异常");
});
try
{
task.Wait();
}
catch (AggregateException ex)
{
// Task的异常会被包装为AggregateException
foreach (var innerEx in ex.InnerExceptions)
{
Console.WriteLine($"捕获到任务异常:{innerEx.Message}");
}
}
}
}
Task与Thread的对比
很多开发者会混淆Task和Thread的使用场景,两者的核心差异如下:
| 对比维度 | Task | Thread |
|---|---|---|
| 抽象层级 | 更高层级的异步操作抽象,不对应具体的线程 | 操作系统线程的直接封装,对应一个具体的线程 |
| 线程使用 | 默认使用线程池线程,可复用,开销小 | 每次创建新线程,开销较大 |
| 返回值支持 | 支持返回结果,通过Task<TResult>获取 | 不直接支持返回值,需要通过共享变量等方式传递 |
| 异常处理 | 异常被捕获并存储在Task.Exception中,支持集中处理 | 异常需要在子线程内部处理,否则会导致线程终止 |
| 任务管理 | 提供丰富的API,支持等待、延续、组合等复杂操作 | API较少,仅支持启动、中断、加入等基本操作 |
Task的常见使用场景
- IO密集型操作:比如文件读写、网络请求等,使用Task可以避免阻塞主线程,提升界面响应速度
- 并行计算:多个独立的任务可以同时执行,提升计算效率
- 异步方法链:结合async/await关键字,可以写出逻辑清晰的异步代码,避免回调地狱
结合async/await使用Task的示例如下:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始执行异步方法");
int result = await CalculateAsync();
Console.WriteLine($"异步方法返回结果:{result}");
Console.WriteLine("异步方法执行完成");
}
static async Task<int> CalculateAsync()
{
Console.WriteLine("异步计算开始");
await Task.Delay(1000); // 模拟异步操作
int sum = 0;
for (int i = 1; i <= 10; i++)
{
sum += i;
}
Console.WriteLine("异步计算结束");
return sum;
}
}