Describe the bug
ParallelHelper.For<TAction>(int start, int end, in TAction action, int minimumActionsPerThread) silently degrades to a single-threaded execution for very large ranges, and can also throw OverflowException for some valid inputs.
The root cause is this computation inside the method:
|
int count = Math.Abs(start - end); |
|
int maxBatches = 1 + ((count - 1) / minimumActionsPerThread); |
start - end is computed in int and can overflow. For the full range [int.MinValue, int.MaxValue) the subtraction wraps around to 1, so count == 1, maxBatches == 1, numBatches == 1, and the method executes the whole loop on the calling thread instead of scheduling work via Parallel.For as the caller explicitly requested.
- For ranges like
[int.MinValue, 0) the subtraction produces exactly int.MinValue, and Math.Abs(int.MinValue) throws OverflowException — the caller gets an exception for an otherwise valid input.
The input is already validated with start > end → throw, so end - start is guaranteed to be non-negative as a mathematical value, and it simply needs to be computed as long to avoid overflow.
Regression
Not a regression as far as I can tell; the code has looked like this for a long time.
Steps to reproduce
Tested on .NET 11, CommunityToolkit.HighPerformance 8.4.2.
using CommunityToolkit.HighPerformance.Helpers;
internal readonly struct ThreadRecordingAction(int[] threadIds) : IAction
{
public void Invoke(int i)
{
// Only sample a few iterations to keep the repro fast
if ((i & 0xFFFFFF) != 0) return;
var slot = i >>> 24;
Volatile.Write(ref threadIds[slot], Environment.CurrentManagedThreadId);
}
}
internal static class Program
{
private static void Main()
{
// Bug #1: silently becomes single-threaded
var ids = new int[256];
ParallelHelper.For(int.MinValue, int.MaxValue, new ThreadRecordingAction(ids), 1);
Array.Sort(ids);
var unique = ids.Where((t, i) => i == 0 || t != ids[i - 1]).Count();
Console.WriteLine($"Distinct thread ids observed: {unique}"); // prints 1 on any multicore machine
// Bug #2: throws OverflowException
try
{
ParallelHelper.For(int.MinValue, 0, new ThreadRecordingAction(new int[1]), 1);
}
catch (OverflowException ex)
{
Console.WriteLine($"Unexpected: {ex}");
}
}
}
Expected behavior
ParallelHelper.For(int.MinValue, int.MaxValue, ..., 1) should distribute work across all available cores, just like it does for any smaller range.
ParallelHelper.For(int.MinValue, 0, ..., 1) should execute successfully and not throw OverflowException.
More generally: for any valid pair (start, end) with start <= end, the method must compute the true element count without relying on int arithmetic that overflows.
IDE and version
Rider
IDE version
JetBrains Rider 2026.1 EAP 9 D
Nuget packages
Nuget package version(s)
8.4.2
Suggested fix
Compute the range as long:
long count = (long)end - start; // always >= 0 after the start > end check
long maxBatches = 1 + ((count - 1) / minimumActionsPerThread);
int numBatches = (int)Math.Min(maxBatches, (long)Environment.ProcessorCount);
...
int batchSize = (int)(1 + ((count - 1) / (long)numBatches));
(batchSize fits into int because it is divided by numBatches <= ProcessorCount, but for full correctness ActionInvoker<TAction> iteration bounds may need to be reviewed too.)
The bug is especially insidious because it does not throw or log anything — the workload simply runs on the calling thread, which looks like the parallel helper is "slow" rather than broken.
Help us help you
Yes, but only if others can assist
Describe the bug
ParallelHelper.For<TAction>(int start, int end, in TAction action, int minimumActionsPerThread)silently degrades to a single-threaded execution for very large ranges, and can also throwOverflowExceptionfor some valid inputs.The root cause is this computation inside the method:
dotnet/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs
Lines 165 to 166 in b135626
start - endis computed inintand can overflow. For the full range[int.MinValue, int.MaxValue)the subtraction wraps around to1, socount == 1,maxBatches == 1,numBatches == 1, and the method executes the whole loop on the calling thread instead of scheduling work viaParallel.Foras the caller explicitly requested.[int.MinValue, 0)the subtraction produces exactlyint.MinValue, andMath.Abs(int.MinValue)throwsOverflowException— the caller gets an exception for an otherwise valid input.The input is already validated with
start > end→ throw, soend - startis guaranteed to be non-negative as a mathematical value, and it simply needs to be computed aslongto avoid overflow.Regression
Not a regression as far as I can tell; the code has looked like this for a long time.
Steps to reproduce
Tested on .NET 11,
CommunityToolkit.HighPerformance8.4.2.Expected behavior
ParallelHelper.For(int.MinValue, int.MaxValue, ..., 1)should distribute work across all available cores, just like it does for any smaller range.ParallelHelper.For(int.MinValue, 0, ..., 1)should execute successfully and not throwOverflowException.More generally: for any valid pair
(start, end)withstart <= end, the method must compute the true element count without relying onintarithmetic that overflows.IDE and version
Rider
IDE version
JetBrains Rider 2026.1 EAP 9 D
Nuget packages
Nuget package version(s)
8.4.2
Suggested fix
Compute the range as
long:(
batchSizefits intointbecause it is divided bynumBatches <= ProcessorCount, but for full correctnessActionInvoker<TAction>iteration bounds may need to be reviewed too.)The bug is especially insidious because it does not throw or log anything — the workload simply runs on the calling thread, which looks like the parallel helper is "slow" rather than broken.
Help us help you
Yes, but only if others can assist