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
- Pass a concrete factory, e.g. new EventLoopScheduler(ts => new Thread(ts) { IsBackground = true, Name = "MyEventLoop" }).
- Use the parameterless EventLoopScheduler() overload if default thread creation is acceptable.
- 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
- Standardize on a factory like ts => new Thread(ts) { IsBackground = true }
- Verify DI container registrations for EventLoopScheduler dependencies
- Use the parameterless overload when custom thread creation is unnecessary
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
- source
- Value cannot be null. (Parameter 'comparer')
- action
- action (Parameter 'action')
- action (Parameter 'action')
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 theView on GitHub (pinned to 94b5d5ab91)