App-vNext/Polly · error · ArgumentNullException
onRetryAsync
Error message
onRetryAsync
What it means
ArgumentNullException is thrown when onRetryAsync is null in WaitAndRetryAsync(IEnumerable<TimeSpan> sleepDurations, Func<Exception,TimeSpan,Context,Task> onRetryAsync). This is the async counterpart of error 205 and wraps into the full 4-parameter delegate.
Source
Thrown at src/Polly/Retry/AsyncRetrySyntax.cs:784
#pragma warning restore 1998
}
/// <summary>
/// Builds an <see cref="AsyncRetryPolicy" /> that will wait and retry as many times as there are provided
/// <paramref name="sleepDurations" />
/// calling <paramref name="onRetryAsync" /> on each retry with the raised exception, the current sleep duration and context data.
/// On each retry, the duration to wait is the current <paramref name="sleepDurations" /> item.
/// </summary>
/// <param name="policyBuilder">The policy builder.</param>
/// <param name="sleepDurations">The sleep durations to wait for on each retry.</param>
/// <param name="onRetryAsync">The action to call asynchronously on each retry.</param>
/// <returns>The policy instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="sleepDurations"/> or <paramref name="onRetryAsync"/> is <see langword="null"/>.</exception>
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Func<Exception, TimeSpan, Context, Task> onRetryAsync)
{
if (onRetryAsync == null)
{
throw new ArgumentNullException(nameof(onRetryAsync));
}
return policyBuilder.WaitAndRetryAsync(
sleepDurations,
onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx));
}
/// <summary>
/// Builds an <see cref="AsyncRetryPolicy" /> that will wait and retry as many times as there are provided
/// <paramref name="sleepDurations" />
/// calling <paramref name="onRetry" /> on each retry with the raised exception, the current sleep duration, retry count, and context data.
/// On each retry, the duration to wait is the current <paramref name="sleepDurations" /> item.
/// </summary>
/// <param name="policyBuilder">The policy builder.</param>
/// <param name="sleepDurations">The sleep durations to wait for on each retry.</param>
/// <param name="onRetry">The action to call on each retry.</param>
/// <returns>The policy instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="sleepDurations"/> or <paramref name="onRetry"/> is <see langword="null"/>.</exception>View on GitHub (pinned to d0e46bdb1e)
Solutions
- Provide a Task-returning lambda accepting Context.
- Use the overload without callback if none is needed.
- Use a no-op default: (ex, delay, ctx) => Task.CompletedTask.
Example fix
// before
.WaitAndRetryAsync(durations, null)
// after
.WaitAndRetryAsync(durations, (ex, delay, ctx) =>
{
logger.LogWarning("Retry {Id} after {Delay}", ctx.OperationKey, delay);
return Task.CompletedTask;
}) Defensive patterns
Strategy: validation
Validate before calling
if (onRetryAsync is null)
onRetryAsync = (_, _, _) => Task.CompletedTask; Type guard
static bool IsValidCallback(Func<Exception, TimeSpan, Context, Task> cb) => cb is not null;
Prevention
- Default async context-aware callbacks to Task.CompletedTask.
- Confirm DI registration before policy construction.
When it happens
Trigger: Calling this overload with a null async callback delegate that includes Context.
Common situations: DI registration that did not bind the callback, or a refactor that removed the callback while keeping the overload choice.
Related errors
- onRetry
- sleepDurations
- sleepDurationProvider
- sleepDurations
- Value must be greater than or equal to zero.
AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13).
Data as JSON: /api/errors/7f8d2e164d92b6a4.
Report an issue: GitHub.