HangfireIO/Hangfire · error · ArgumentNullException

taskScheduler

Error message

taskScheduler

What it means

Thrown by BackgroundDispatcherAsync's constructor (internal) when the TaskScheduler argument is null. The dispatcher schedules each dispatch loop task onto that scheduler (Task.Factory.StartNew(..., _taskScheduler)), so a null scheduler has nowhere to run.

Source

Thrown at src/Hangfire.Core/Processing/BackgroundDispatcherAsync.cs:52

        private readonly object _state;

        private readonly TaskScheduler _taskScheduler;
        private readonly bool _ownsScheduler;

        public BackgroundDispatcherAsync(
            [NotNull] IBackgroundExecution execution,
            [NotNull] Func<Guid, object, Task> action,
            [CanBeNull] object state,
            [NotNull] TaskScheduler taskScheduler,
            int maxConcurrency,
            bool ownsScheduler)
        {
            if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency));

            _execution = execution ?? throw new ArgumentNullException(nameof(execution));
            _action = action ?? throw new ArgumentNullException(nameof(action));
            _state = state;
            _taskScheduler = taskScheduler ?? throw new ArgumentNullException(nameof(taskScheduler));
            _ownsScheduler = ownsScheduler;

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

            _stopped = new CountdownEvent(maxConcurrency);

            for (var i = 0; i < maxConcurrency; i++)
            {
                Task.Factory.StartNew(
                    DispatchLoop,
                    CancellationToken.None,
                    TaskCreationOptions.None,
                    _taskScheduler).Unwrap();
            }
        }

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Pass a non-null TaskScheduler — typically a BackgroundTaskScheduler instance or TaskScheduler.Default.
  2. Resolve the scheduler before constructing the dispatcher and validate it is non-null.
  3. Fix the DI registration so the scheduler service is always provided.

Example fix

// before
var dispatcher = new BackgroundDispatcherAsync(execution, action, state, null, 4, false);

// after
var scheduler = scheduler ?? new BackgroundTaskScheduler();
var dispatcher = new BackgroundDispatcherAsync(execution, action, state, scheduler, 4, ownsScheduler: true);
Defensive patterns

Strategy: validation

Validate before calling

TaskScheduler scheduler = resolvedScheduler ?? new BackgroundTaskScheduler();

Prevention

When it happens

Trigger: Constructing BackgroundDispatcherAsync with taskScheduler == null. Happens when the caller passes a null BackgroundTaskScheduler or TaskScheduler.Default resolution failed.

Common situations: A hosting extension resolves the scheduler lazily and the resolution returned null; a custom server builder omitted the scheduler argument; DI container misregistration for the scheduler service.

Related errors


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