dotnet/reactive · error · ArgumentNullException
scheduler
Error message
scheduler
What it means
The TakeUntil(source, endTime, scheduler) overload throws ArgumentNullException because the scheduler argument was null. The operator needs an IAsyncScheduler to schedule the end-time timer; the library rejects a null scheduler immediately with the parameter name 'scheduler' in the message. Note this overload validates both source and scheduler, so either being null yields this exception with the corresponding name.
Solutions
- Pass an explicit scheduler such as TaskPoolAsyncScheduler.Default or a NewThreadScheduler instance.
- Use the simpler TakeUntil(source, endTime) overload, which defaults to an appropriate scheduler.
- If the scheduler comes from configuration, validate it is bound before building the pipeline.
Example fix
// before var bounded = source.TakeUntil(endTime, null); // after var bounded = source.TakeUntil(endTime, TaskPoolAsyncScheduler.Default);
Defensive patterns
Strategy: validation
Validate before calling
if (scheduler == null) scheduler = TaskPoolAsyncScheduler.Default;
Type guard
bool HasScheduler(IAsyncScheduler? s) => s is not null;
Try / catch
try { var bounded = source.TakeUntil(endTime, scheduler); } catch (ArgumentNullException ex) when (ex.ParamName == "scheduler") { var bounded = source.TakeUntil(endTime); } Prevention
- Default to TaskPoolAsyncScheduler.Default unless you have a specific timing/dispatch requirement.
- Register the scheduler in DI with a non-null default binding.
- Use the scheduler-less TakeUntil overload to let the library pick a default.
When it happens
Trigger: Calling AsyncObservable.TakeUntil(source, endTime, null) with a null IAsyncScheduler.
Common situations: A scheduler resolved from DI/configuration that failed to bind; passing a variable of type IAsyncScheduler that was never assigned; test code where the mock scheduler factory returned null.
Related errors
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/efcd8ef80046a65a.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/TakeUntil.cs:65
return CreateAsyncObservable<TSource>.From(
source,
endTime,
static async (source, endTime, observer) =>
{
var (sourceObserver, timer) = await AsyncObserver.TakeUntil(observer, endTime).ConfigureAwait(false);
var subscription = await source.SubscribeSafeAsync(sourceObserver).ConfigureAwait(false);
return StableCompositeAsyncDisposable.Create(subscription, timer);
});
}
public static IAsyncObservable<TSource> TakeUntil<TSource>(this IAsyncObservable<TSource> source, DateTimeOffset endTime, IAsyncScheduler scheduler)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (scheduler == null)
throw new ArgumentNullException(nameof(scheduler));
// REVIEW: May be easier to just use TakeUntil with a Timer parameter. Do we want TakeUntil on the observer?
return CreateAsyncObservable<TSource>.From(
source,
(endTime, scheduler),
static async (source, state, observer) =>
{
var (sourceObserver, timer) = await AsyncObserver.TakeUntil(observer, state.endTime, state.scheduler).ConfigureAwait(false);
var subscription = await source.SubscribeSafeAsync(sourceObserver).ConfigureAwait(false);
return StableCompositeAsyncDisposable.Create(subscription, timer);
});
}
}
public partial class AsyncObserverView on GitHub (pinned to 94b5d5ab91)