PLINQ中ForAll与WithCancellation组合无法中途取消查询的问题
PLINQ中WithCancellation与ForAll组合的取消行为解析
这是PLINQ的设计行为,既不是使用错误,也不是库的bug。以下是具体解析:
问题重现
复现代码
CancellationTokenSource cts = new CancellationTokenSource(1000); cts.Token.Register(() => Console.WriteLine("--Token Canceled")); try { Enumerable.Range(1, 20) .AsParallel() .WithDegreeOfParallelism(2) .WithCancellation(cts.Token) .ForAll(x => { Console.WriteLine($"Processing item #{x}"); Thread.Sleep(200); //cts.Token.ThrowIfCancellationRequested(); }); Console.WriteLine($"The query was completed successfully"); } catch (OperationCanceledException) { Console.WriteLine($"The query was canceled"); }
不符合预期的输出
Processing item #1 Processing item #2 Processing item #4 Processing item #3 Processing item #5 Processing item #6 Processing item #8 Processing item #7 Processing item #10 Processing item #9 --Token Canceled Processing item #11 Processing item #12 Processing item #13 Processing item #14 Processing item #15 Processing item #16 Processing item #17 Processing item #19 Processing item #20 Processing item #18 The query was canceled
符合预期的输出(取消注释ThrowIfCancellationRequested后)
Processing item #2 Processing item #1 Processing item #3 Processing item #4 Processing item #5 Processing item #6 Processing item #7 Processing item #8 Processing item #9 Processing item #10 --Token Canceled The query was canceled
行为原因解析
WithCancellation的作用边界:
PLINQ的WithCancellation并非用来强制中断正在执行的委托代码,它的核心作用是:- 阻止PLINQ调度新的任务/处理新的项;
- 在查询的内部检查点(比如分区分配、任务调度节点)检测取消令牌,触发查询终止流程。
但对于已经启动的委托,PLINQ不会主动终止线程或打断正在运行的逻辑。
ForAll的执行特性:ForAll会遍历所有分区并执行委托逻辑,一旦某个项对应的委托开始执行,PLINQ会等待该委托完成,除非委托自身主动响应取消。你看到最终抛出OperationCanceledException,是因为查询执行完毕后,PLINQ检测到令牌已处于取消状态,才抛出异常,但此时所有项都已处理完成。
正确用法
要实现中途及时取消,必须在ForAll的委托逻辑中主动检查取消令牌,在合适的时机(比如耗时操作前后)调用cts.Token.ThrowIfCancellationRequested(),这样当令牌触发取消时,正在执行的委托会立即抛出异常,PLINQ会停止调度新项,并终止整个查询流程。
内容的提问来源于stack exchange,提问作者Theodor Zoulias
相关产品推荐
相关产品推荐

