dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'threadFactory')

Error message

Value cannot be null. (Parameter 'threadFactory')

What it means

The EventLoopScheduler constructor requires a Func<ThreadStart, Thread> factory used to create the scheduler's dedicated thread. Passing null triggers the null-coalescing throw expression, raising ArgumentNullException before the stopwatch, gate, semaphore, and queue are initialized.

Solutions

  1. Pass a concrete factory, e.g. new EventLoopScheduler(ts => new Thread(ts) { IsBackground = true, Name = "MyEventLoop" }).
  2. Use the parameterless EventLoopScheduler() overload if default thread creation is acceptable.
  3. Fix DI registration so a non-null factory is provided; validate it at composition time.

Example fix

// before
var scheduler = new EventLoopScheduler(factory); // factory null

// after
var scheduler = new EventLoopScheduler(ts => new Thread(ts) { IsBackground = true, Name = "EventLoop" });
Defensive patterns

Strategy: validation

Validate before calling

if (threadFactory is null)
    throw new ArgumentNullException(nameof(threadFactory));
var scheduler = new EventLoopScheduler(threadFactory);

Type guard

bool IsValid(Func<ThreadStart, Thread>? f) => f is not null;

Try / catch

try { var s = new EventLoopScheduler(threadFactory); } catch (ArgumentNullException ex) when (ex.ParamName == "threadFactory") { /* fall back to new EventLoopScheduler() or fix DI registration */ }

Prevention

When it happens

Trigger: new EventLoopScheduler(null) or new EventLoopScheduler(someFactoryVariable) where the factory variable is null, commonly from DI resolution or a config-supplied factory that failed to load.

Common situations: Container registrations missing the thread factory, reflection-based instantiation supplying null, or code migrated from the parameterless EventLoopScheduler() overload that accidentally passes null instead.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/18395efc8756c866. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/EventLoopScheduler.cs:96

        #region Constructors

        /// <summary>
        /// Creates an object that schedules units of work on a designated thread.
        /// </summary>
        public EventLoopScheduler()
            : this(static a => new Thread(a) { Name = "Event Loop " + Interlocked.Increment(ref _counter), IsBackground = true })
        {
        }

        /// <summary>
        /// Creates an object that schedules units of work on a designated thread, using the specified factory to control thread creation options.
        /// </summary>
        /// <param name="threadFactory">Factory function for thread creation.</param>
        /// <exception cref="ArgumentNullException"><paramref name="threadFactory"/> is <c>null</c>.</exception>
        public EventLoopScheduler(Func<ThreadStart, Thread> threadFactory)
        {
            _threadFactory = threadFactory ?? throw new ArgumentNullException(nameof(threadFactory));
            _stopwatch = ConcurrencyAbstractionLayer.Current.StartStopwatch();

            _gate = new object();

            _evt = new SemaphoreSlim(0);
            _queue = new SchedulerQueue<TimeSpan>();
            _readyList = new Queue<ScheduledItem<TimeSpan>>();

            ExitIfEmpty = false;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Indicates whether the event loop thread is allowed to quit when no work is left. If new work
        /// is scheduled afterwards, a new event loop thread is created. This property is used by the

View on GitHub (pinned to 94b5d5ab91)