HangfireIO/Hangfire · error · ArgumentNullException

threadFactory

Error message

threadFactory

What it means

Thrown by the public BackgroundTaskScheduler(Func<ThreadStart, IEnumerable<Thread>>, Action<Exception>) constructor when the threadFactory argument is null. The scheduler depends entirely on the factory to create its dedicated worker threads, so a null factory cannot proceed.

Source

Thrown at src/Hangfire.Core/Processing/BackgroundTaskScheduler.cs:109

            : this(threadStart => DefaultThreadFactory(threadStart, threadCount), DefaultExceptionHandler)
        {
        }

        /// <summary>Initializes a new instance of the <see cref="BackgroundTaskScheduler"/>
        /// class with the specified <paramref name="threadFactory"/> and an optional exception
        /// handler. All the created threads will be started to dispatch <see cref="Task"/>
        /// instances scheduled to run on this scheduler.</summary>
        /// <param name="threadFactory">Callback that creates one or more dedicated threads.</param>
        /// <param name="exceptionHandler">Optional callback that is invoked when unhandled exception occurs 
        /// in one of the threads. After this event this instance is considered stopped.</param>
        /// <exception cref="ArgumentNullException"><paramref name="threadFactory"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><paramref name="threadFactory"/> returned <see langword="null"/> or zero threads.</exception>
        /// <exception cref="ArgumentException"><paramref name="threadFactory"/> returned at least one thread not in the <see cref="ThreadState.Unstarted"/> state.</exception>
        public BackgroundTaskScheduler(
            [NotNull] Func<ThreadStart, IEnumerable<Thread>> threadFactory,
            [CanBeNull] Action<Exception> exceptionHandler)
        {
            if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory));

            _exceptionHandler = exceptionHandler;
            _semaphore = new Semaphore(0, Int32.MaxValue);

            // Stopped event should always be the first in this array, see the DispatchLoop method.
            _waitHandles = new WaitHandle[] { _stopped, _semaphore };

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

            _threads = threadFactory(DispatchLoop)?.ToArray();

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

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Pass a non-null Func<ThreadStart, IEnumerable<Thread>>; use the parameterless ctor or the (int) ctor if you want the built-in factory.
  2. Guard the call site: throw a descriptive exception if the factory is null before constructing.
  3. Verify the source producing the factory is initialised.

Example fix

// before
var scheduler = new BackgroundTaskScheduler(null, ex => Log(ex));

// after
var scheduler = new BackgroundTaskScheduler(
    start => Enumerable.Range(0, 4).Select(i => new Thread(start) { IsBackground = true, Name = $"Worker #{i}" }),
    ex => Log(ex));
Defensive patterns

Strategy: validation

Validate before calling

if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory));
var scheduler = new BackgroundTaskScheduler(threadFactory, handler);

Try / catch

try { var s = new BackgroundTaskScheduler(factory, handler); }
catch (ArgumentNullException ex) when (ex.ParamName == nameof(threadFactory))
{ factory = DefaultFactory; /* retry */ }

Prevention

When it happens

Trigger: Calling `new BackgroundTaskScheduler(null, handler)` or passing a null threadFactory. Also reachable if a caller resolves the factory lazily and it comes back null.

Common situations: Custom hosting code that builds a BackgroundTaskScheduler with a conditional factory that evaluated to null; a refactor that removed the factory expression; DI misregistration returning null for the factory delegate.

Related errors


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