HangfireIO/Hangfire · error · ArgumentException

At least one unstarted thread should be created.

Error message

At least one unstarted thread should be created.

What it means

Thrown as ArgumentException (message 'At least one unstarted thread should be created.') by the BackgroundDispatcher constructor when the threadFactory returns null or an empty collection. BackgroundDispatcher needs at least one worker thread to run the dispatch loop; zero threads means no processing can occur, so the constructor rejects it at line 59.

Source

Thrown at src/Hangfire.Core/Processing/BackgroundDispatcher.cs:59

            [NotNull] Action<Guid, object> action,
            [CanBeNull] object state,
            [NotNull] Func<ThreadStart, IEnumerable<Thread>> threadFactory)
        {
            if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory));

            _execution = execution ?? throw new ArgumentNullException(nameof(execution));
            _action = action ?? throw new ArgumentNullException(nameof(action));
            _state = state;

#if !NETSTANDARD1_3
            AppDomainUnloadMonitor.EnsureInitialized();
#endif

            var threads = threadFactory(DispatchLoop)?.ToArray();

            if (threads == null || threads.Length == 0)
            {
                throw new ArgumentException("At least one unstarted thread should be created.", nameof(threadFactory));
            }

            if (threads.Any(static thread => thread == null || (thread.ThreadState & ThreadState.Unstarted) == 0))
            {
                throw new ArgumentException("All the threads should be non-null and in the ThreadState.Unstarted state.", nameof(threadFactory));
            }

            _stopped = new CountdownEvent(threads.Length);

            foreach (var thread in threads)
            {
                thread.Start();
            }
        }

        public bool Wait(TimeSpan timeout)
        {
            return _stopped.WaitHandle.WaitOne(timeout);

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Ensure the threadFactory always returns at least one Thread (matching the configured worker/concurrency count, minimum 1).
  2. Validate the configured worker count is >= 1 before constructing the dispatcher or its factory.
  3. If the factory can fail to allocate a thread, throw a descriptive exception there rather than returning an empty collection.

Example fix

// before
Func<ThreadStart, IEnumerable<Thread>> factory =
    start => Enumerable.Empty<Thread>(); // throws

// after
Func<ThreadStart, IEnumerable<Thread>> factory =
    start => Enumerable.Range(0, Math.Max(1, workerCount))
        .Select(_ => new Thread(start) { IsBackground = true });
Defensive patterns

Strategy: validation

Validate before calling

if (workerCount < 1) throw new ArgumentOutOfRangeException(nameof(workerCount));
Func<ThreadStart, IEnumerable<Thread>> factory =
    start => Enumerable.Range(0, workerCount)
        .Select(_ => new Thread(start) { IsBackground = true });

Prevention

When it happens

Trigger: Constructing a BackgroundDispatcher where the supplied Func<ThreadStart, IEnumerable<Thread>> returns null or an empty sequence (e.g. a factory that filters out all threads, returns Array.Empty<Thread>(), or yields no elements). The check at lines 57-60 fires before any thread is started.

Common situations: A custom threadFactory that conditionally returns an empty collection (e.g. based on a worker count of zero, or a thread-creation throttle that returned nothing); a factory that returns null on a resource-allocation failure; misconfiguration where the worker/thread count is zero. Because BackgroundDispatcher is internal, this normally surfaces only via custom processing servers or tests.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/b74f8c987bedeb88. Report an issue: GitHub.