stride3d/stride · error · ArgumentOutOfRangeException

Thread count must be positive.

Error message

Thread count must be positive.

What it means

DispatcherWrapper's constructor validates that the number of worker threads dispatched per invocation is strictly positive, since it sizes WorkerBufferPools and schedules work across threads. A zero or negative threadCount makes dispatch meaningless and would corrupt pool sizing, so it throws ArgumentOutOfRangeException.

Solutions

  1. Pass at least 1 thread: new DispatcherWrapper(Math.Max(1, threadCount));
  2. Fix the thread-count source so it yields a positive value (e.g. Environment.ProcessorCount).
  3. Use the default constructor path / library defaults instead of hand-rolling a dispatcher.

Example fix

// before
var dispatcher = new DispatcherWrapper(config.ThreadCount); // 0
// after
var dispatcher = new DispatcherWrapper(Math.Max(1, config.ThreadCount));
Defensive patterns

Strategy: validation

Validate before calling

int threadCount = Math.Max(1, configuredThreadCount);
var dispatcher = new DispatcherWrapper(threadCount);

Try / catch

try { d = new DispatcherWrapper(cfg.ThreadCount); } catch (ArgumentOutOfRangeException) { d = new DispatcherWrapper(Environment.ProcessorCount); }

Prevention

When it happens

Trigger: Constructing new DispatcherWrapper(0) or with a negative value, typically from a thread-count setting read from config/environment that defaulted to 0 or wasn't initialized.

Common situations: Environment.ProcessorCount returning unexpectedly, config parsing producing 0, or custom BepuSimulation threading setup passing an unset variable.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/02170dc6bbfe8631. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs:1117

        private volatile bool _disposed;

        /// <inheritdoc/>
        public WorkerBufferPools WorkerPools { get; private set; }
        /// <inheritdoc/>
        public void* UnmanagedContext => _unmanagedContext;
        /// <inheritdoc/>
        public object? ManagedContext => _managedContext;

        /// <summary>
        /// Creates a new thread dispatcher with the given number of threads.
        /// </summary>
        /// <param name="threadCount">Number of threads to dispatch on each invocation.</param>
        /// <param name="threadPoolBlockAllocationSize">Size of memory blocks to allocate for thread pools.</param>
        public DispatcherWrapper(int threadCount, int threadPoolBlockAllocationSize = 16384)
        {
            if (threadCount <= 0)
                throw new ArgumentOutOfRangeException(nameof(threadCount), "Thread count must be positive.");
            _threadCount = threadCount;
            WorkerPools = new WorkerBufferPools(threadCount, threadPoolBlockAllocationSize);
        }

        private void DispatchThread(int workerIndex)
        {
            switch (_workerType)
            {
                case WorkerType.Managed: _managedWorker!(workerIndex); break;
                case WorkerType.Unmanaged: _unmanagedWorker(workerIndex, this); break;
            }
        }

        private void SignalThreads(int maximumWorkerCount)
        {
            int workersToSignal = maximumWorkerCount < _threadCount ? maximumWorkerCount : _threadCount;
            Dispatcher.ForBatched(workersToSignal, new Job(this));
        }

View on GitHub (pinned to 96fad776d2)