Task.Run +Task.WhenAll 与 Paraller之一
在C#中,Task.Run
配合Task.WhenAll
和Parallel
类都是用于并行处理以提高性能的常见方法,但它们的使用场景和内部机制有所不同。
1. Task.Run + Task.WhenAll
这种方法适用于I/O密集型操作(如网络请求、文件读写等)或CPU密集型操作,但更侧重于异步操作。它通过将多个任务并行启动,然后等待所有任务完成。
使用场景:
- 当操作是异步的(如使用
async
/await
)时,这种方法更自然。 - 每个任务相对独立,不需要共享资源(如果共享资源,需要注意线程安全)。
- 任务数量不是特别大(虽然可以创建很多任务,但要注意资源限制)。
2. Parallel类
Parallel
类(Parallel.For
,Parallel.ForEach
)主要用于数据并行,适用于CPU密集型操作,它使用多个线程来并行处理数据集合。它是基于任务的,但内部使用线程池,并且是同步的(阻塞当前线程直到所有操作完成)。
使用场景:
- CPU密集型操作,例如大量计算。
- 处理集合中的每个元素,且每个元素的处理是独立的(或通过线程安全的方式共享状态)。
- 当操作是同步的,并且你希望利用多核处理器。
在C#中,Task.Run
+ Task.WhenAll
和 Parallel
类都是并行处理的技术,但适用场景和实现方式不同。以下是选择建议和对应的取消操作实现:
一、如何选择?
特性 | Task.Run + Task.WhenAll | Parallel |
---|---|---|
适用场景 | I/O密集型操作、异步任务、独立任务 | CPU密集型操作、数据并行处理 |
并行控制 | 手动控制(通过任务列表) | 自动分区(通过ParallelOptions 配置) |
阻塞性 | 非阻塞(async/await) | 阻塞(同步操作) |
任务粒度 | 粗粒度(独立任务) | 细粒度(数据集合元素) |
异常处理 | 通过AggregateException 捕获所有异常 |
同左 |
资源开销 | 较高(每个任务独立调度) | 较低(优化线程池使用) |
选择建议:
-
优先用
Task.WhenAll
:- 处理 I/O密集型 操作(如API调用、文件读写)
- 需要 异步等待 结果时(避免阻塞线程)
- 任务逻辑 独立且数量动态变化 时
-
优先用
Parallel
:- 处理 CPU密集型 计算(如图像处理、数值计算)
- 需要 高效处理数据集合(如数组、列表的并行循环)
- 需要 限制并发度(通过
MaxDegreeOfParallelism
)
二、取消操作实现
1. Task.Run
+ Task.WhenAll
的取消
使用 CancellationTokenSource
传递取消令牌。
public async Task RunTasksWithCancellationAsync()
{
var cts = new CancellationTokenSource();
// 创建任务列表(传入取消令牌)
var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
tasks.Add(Task.Run(() => DoWorkAsync(cts.Token), cts.Token));
}
// 外部触发取消(例如超时或用户操作)
cts.CancelAfter(TimeSpan.FromSeconds(5)); // 5秒后自动取消
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
Console.WriteLine("任务已取消");
}
}
private async Task DoWorkAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await Task.Delay(1000, token); // 模拟异步操作
token.ThrowIfCancellationRequested(); // 检查取消
}
}
2. Parallel
的取消
通过 ParallelOptions
传递取消令牌。
public void RunParallelWithCancellation()
{
var cts = new CancellationTokenSource();
var options = new ParallelOptions
{
CancellationToken = cts.Token,
MaxDegreeOfParallelism = 4 // 限制并发数
};
cts.CancelAfter(TimeSpan.FromSeconds(5)); // 5秒后取消
try
{
Parallel.For(0, 100, options, (i, state) =>
{
options.CancellationToken.ThrowIfCancellationRequested();
DoCpuBoundWork(i); // 模拟CPU密集型操作
});
}
catch (OperationCanceledException)
{
Console.WriteLine("并行循环已取消");
}
}
private void DoCpuBoundWork(int index)
{
Thread.Sleep(500); // 模拟CPU工作
}
三、关键注意事项
- 资源释放:
- 在取消后及时释放资源(如数据库连接、文件句柄)。
- 取消响应:
- 在循环或长时间操作中 定期检查
token.IsCancellationRequested
。
- 在循环或长时间操作中 定期检查
- 异常聚合:
- 两者都会抛出
AggregateException
,需遍历InnerExceptions
处理具体错误。
- 两者都会抛出
- 异步兼容性:
Parallel
不支持async
委托(内部用同步方法),若需异步并行循环,使用Task.WhenAll
。
四、总结
Task.WhenAll
:适合 异步、I/O密集型、异构任务。Parallel
:适合 同步、CPU密集型、同构数据并行。- 取消机制:统一通过
CancellationToken
实现,注意在任务内部定期检查取消状态。