Cysharp/UniTask · error · ArgumentOutOfRangeException

Delay does not allow minus delayTimeSpan. delayTimeSpan:

Error message

Delay does not allow minus delayTimeSpan. delayTimeSpan:

What it means

Thrown by UniTask.Delay(TimeSpan) when delayTimeSpan is negative. UniTask delays accumulate elapsed time against the target duration; a negative duration cannot elapse. The guard catches sign errors early, before the promise is created.

Source

Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs:169

        }

        public static UniTask Delay(TimeSpan delayTimeSpan, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
        {
            var delayType = ignoreTimeScale ? DelayType.UnscaledDeltaTime : DelayType.DeltaTime;
            return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken, cancelImmediately);
        }

        public static UniTask Delay(int millisecondsDelay, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
        {
            var delayTimeSpan = TimeSpan.FromMilliseconds(millisecondsDelay);
            return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken, cancelImmediately);
        }

        public static UniTask Delay(TimeSpan delayTimeSpan, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
        {
            if (delayTimeSpan < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException("Delay does not allow minus delayTimeSpan. delayTimeSpan:" + delayTimeSpan);
            }

#if UNITY_EDITOR
            // force use Realtime.
            if (PlayerLoopHelper.IsMainThread && !UnityEditor.EditorApplication.isPlaying)
            {
                delayType = DelayType.Realtime;
            }
#endif

            switch (delayType)
            {
                case DelayType.UnscaledDeltaTime:
                    {
                        return new UniTask(DelayIgnoreTimeScalePromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token);
                    }
                case DelayType.Realtime:
                    {

View on GitHub (pinned to ceac8d6946)

Solutions

  1. Clamp the TimeSpan to zero: delayTimeSpan < TimeSpan.Zero ? TimeSpan.Zero : delayTimeSpan
  2. Check if the deadline has already passed and skip the delay entirely
  3. Use TimeSpan.Max(TimeSpan.Zero, computedSpan) (C# 8.0+ for TimeSpan supports CompareTo, otherwise use a ternary)

Example fix

// before
var wait = deadline - DateTime.UtcNow; // could be negative
await UniTask.Delay(wait);

// after
var wait = deadline - DateTime.UtcNow;
if (wait > TimeSpan.Zero)
    await UniTask.Delay(wait);
Defensive patterns

Strategy: validation

Validate before calling

TimeSpan safeDelay = delayTimeSpan < TimeSpan.Zero ? TimeSpan.Zero : delayTimeSpan;
await UniTask.Delay(safeDelay);

Prevention

When it happens

Trigger: Calling UniTask.Delay(TimeSpan.FromMilliseconds(-100)), or passing a TimeSpan computed from a subtraction that yields a negative result (e.g., deadline - now when the deadline has already passed).

Common situations: Timeout calculations where the deadline has already passed: TimeSpan.FromSeconds(targetTime - Time.time). Deserialization or configuration that produces negative durations. Clock skew in network time calculations.

Related errors


AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13). Data as JSON: /api/errors/cc492649953b980e. Report an issue: GitHub.