c# Task Parallel Library (TPL) 的核心组件有哪些

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

Task 类是 TPL 的基础执行单元

所有异步工作最终都封装在

Task
Task<tresult></tresult>
实例中。它不等同于线程,而是“一个可能异步完成的操作”的抽象——可以由线程池线程、IOCP、甚至同步执行(比如
Task.FromResult(42)
)来完成。

常见误区是认为

new Task(...).Start()
是推荐写法,实际上应优先用静态工厂方法:

Task.Run(() => DoWork());           // 推荐:自动调度到线程池
Task.Factory.StartNew(() => DoWork()); // 较老 API,需注意默认调度器和参数重载歧义
Task.CompletedTask;                  // 零开销已完成任务,替代 Task.FromResult(())

注意:

Task
默认不捕获同步上下文(如 UI 线程),若需回到原始上下文(如 WinForms/WPF 更新控件),得显式用
await task.ConfigureAwait(true)

TaskScheduler 控制任务如何排队与执行

TaskScheduler
是任务调度策略的抽象。默认是
ThreadPoolTaskScheduler
,但你可以自定义,比如实现单线程调度器用于串行化 UI 相关操作,或限制并发数的调度器。

关键点:

TaskScheduler.Current
返回当前上下文的调度器(常用于
Task.Factory.StartNew
taskScheduler:
参数)
TaskScheduler.Default
指向线程池调度器
自定义调度器必须重写
QueueTask
GetScheduledTasks
(后者仅调试用途,可抛
NotSupportedException

不要直接 new 一个

TaskScheduler
并调用
QueueTask
——它不保证执行,只是入队;真正触发执行依赖调度器内部循环或外部驱动(如 Windows 消息泵)。

Parallel 类提供数据并行的高层封装

Parallel.For
Parallel.ForEach
Parallel.Invoke
是面向集合/范围的并行构造,底层仍基于
Task
,但自动处理分区、负载均衡、异常聚合(抛
AggregateException
)。

使用时注意:

迭代体必须是无副作用或线程安全的;不要在循环里直接修改共享变量,改用
ParallelOptions.MaxDegreeOfParallelism
限流或
ConcurrentBag<t></t>
等线程安全集合
Parallel.ForEach(source, item => { ... })
中的
source
若是
IEnumerable<t></t>
,每次枚举都重新遍历——避免传入含副作用的迭代器(如数据库游标)
它不返回结果集;需要结果请改用 PLINQ(
AsParallel().Select(...).ToArray()

PLINQ 是 Parallel + LINQ,不是独立组件而是 TPL 的查询扩展

AsParallel()
IEnumerable<t></t>
转为
ParallelQuery<t></t>
,后续 LINQ 操作(
Where
Select
Sum
等)自动并行化。但它不是万能加速器:

小数据集(x => x * 2)反而因分区开销变慢 顺序敏感操作(如
First()
Take(1)
)会退化为部分并行,且结果不保证稳定
必须显式调用
AsOrdered()
才保持源顺序,但代价是降低吞吐量

错误示例:

list.AsParallel().Select(x => riskySideEffect()).ToList()
—— 副作用无法预测执行时机和次数,极易引发竞态。

真正难的不是记住这些组件名,而是判断该用

Task.Run
还是
Parallel.ForEach
,或者干脆避开 TPL 改用
ValueTask
IAsyncEnumerable<t></t>
。线程调度、上下文捕获、异常传播、取消令牌传递——每个环节漏掉一个细节,都会让并行逻辑在高负载下间歇性失败。

相关推荐