parallel.for支持合作式取消,需通过paralleloptions传入cancellationtoken,检查在每次迭代前进行,正在执行的迭代不会中断,必须捕获operationcanceledexception。

Parallel.For 本身支持取消,但必须传入 CancellationToken
默认的 Parallel.For(int, int, Action<int>)</int> 重载不接受取消令牌,所以直接调用无法中途退出。真正支持取消的是带 ParallelOptions 的重载——你得显式构造并传入 CancellationToken。
常见错误是只创建了 CancellationTokenSource 却没把它塞进 ParallelOptions,结果调用 Cancel() 完全没反应。
-
Parallel.For检测取消是“合作式”的:它只在每次迭代开始前检查令牌是否已取消,不会中断正在执行的迭代体 - 如果某次循环体耗时很长(比如 IO 或计算密集),该次迭代仍会跑完,之后才退出整个循环
- 必须捕获
OperationCanceledException,否则异常会向上抛出并终止整个程序(除非你用try/catch包裹Parallel.For调用)
正确写法:用 ParallelOptions + CancellationToken
下面是最小可运行示例,演示如何触发取消并安全退出:
var cts = new CancellationTokenSource();
var options = new ParallelOptions
{
CancellationToken = cts.Token,
MaxDegreeOfParallelism = Environment.ProcessorCount
};
try
{
Parallel.For(0, 1000, options, i =>
{
// 模拟工作:每 100 次迭代检查一次是否该退出(避免太频繁检查)
if (i % 100 == 0) options.CancellationToken.ThrowIfCancellationRequested();
Thread.Sleep(10); // 模拟耗时操作
Console.WriteLine($"Working on {i}");
});
}
catch (OperationCanceledException)
{
Console.WriteLine("Parallel.For was cancelled.");
}
finally
{
cts.Dispose();
}
注意:ThrowIfCancellationRequested() 是推荐做法,比手动检查 IsCancellationRequested 更安全(它还会校验 IsDisposed 等状态)。
取消后如何知道哪些项已完成、哪些被跳过?
Parallel.For 不提供“已处理索引列表”这类返回值。如果你需要精确追踪进度或恢复断点,它不是合适工具——考虑改用 Partition + Task.Run + await Task.WhenAny 手动编排,或者用 PLINQ 的 AsParallel().WithCancellation() 配合 ToList() 或 ForAll()。
-
Parallel.For的设计目标是吞吐优先,不是状态可控 - 取消发生后,
ParallelLoopState.LowestBreakIteration在Break()场景下才有意义;对CancellationToken无效 - 不要依赖循环变量
i的最终值来判断范围——它可能停留在任意位置,且无定义
替代方案:为什么有时该放弃 Parallel.For?
当你需要以下任一能力时,Parallel.For 就显得力不从心:
- 逐个 await 异步操作(
Parallel.For不支持async/await) - 动态调整任务粒度(比如按数据块分发,而非固定索引)
- 取消后获取剩余未处理项列表
- 细粒度错误分类(如区分取消、超时、业务异常)
这时候更稳妥的做法是用 Task.Run 启动一组 Task,用 Task.WhenAny 监听完成或取消,并自行维护状态。虽然代码多几行,但控制权完全在你手里。
真正容易被忽略的一点:即使你传了 CancellationToken,只要循环体内部有阻塞调用(比如没加 await 的 HttpClient.SendAsync),取消信号也会被卡住——因为线程被占着,Parallel.For 没机会检查令牌。这种场景必须用异步替代同步,或把耗时操作移到单独线程并配合 cts.Token.Register 主动响应。










