dotnet/wpf · error · InvalidOperationException

SR.MediaContext_InfiniteTickLoop

Error message

SR.MediaContext_InfiniteTickLoop

What it means

RenderMessageHandlerCore limits its animation tick loop to 153 iterations; if the time manager still needs ticking after that many passes (layout adding more animations each pass), it throws InvalidOperationException(SR.MediaContext_InfiniteTickLoop). This guards against an unbounded layout/animation feedback loop.

Solutions

  1. Break the feedback loop: don't start/modify animations from LayoutUpdated or SizeChanged for the same element being animated.
  2. Animate render transforms (TranslateTransform/ScaleTransform) instead of layout-affecting properties (Width, Margin).
  3. Defer new animation starts via Dispatcher.BeginInvoke at Background priority so they apply after the tick loop ends.
  4. Use Completed events rather than re-triggering on every layout pass.

Example fix

// before
private void OnLayoutUpdated(object s, EventArgs e) {
    myAnim.BeginAnimation(WidthProperty, anim); // re-triggers layout -> loop
}
// after
private void OnLoaded(object s, RoutedEventArgs e) {
    var tt = new TranslateTransform();
    el.RenderTransform = tt;
    tt.BeginAnimation(TranslateTransform.XProperty, anim); // no layout change
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard against re-entrant animation triggers:
private bool _animating;
void OnSizeChanged(object s, SizeChangedEventArgs e) {
  if (_animating) return; // never restart the animation that causes the resize
  _animating = true; StartAnimation();
}

Try / catch

try { RunAnimatedLayout(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("tick")) { CancelAnimations(); Log("animation/layout feedback loop"); }

Prevention

When it happens

Trigger: A composition-target render pass in which each layout pass adds new animations or re-invalidates the timing tree, so the do/while loop keeps ticking forever — typically caused by animation that changes the layout of what drives it.

Common situations: SizeChanged / LayoutUpdated handlers starting new animations, animating a property whose change re-measures a container that re-triggers the animation, custom panels with feedback between measure and animated properties.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/b0fcd26d54f99d3e. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/MediaContext.cs:1792

            _isRendering = true;

            // We don't need our promotion timers anymore.
            _promoteRenderOpToInput.Stop();
            _promoteRenderOpToRender.Stop();

            bool gotException = true;

            try
            {
                int tickLoopCount = 0;


                do
                {
                    tickLoopCount++;
                    if (tickLoopCount > 153)
                    {
                        throw new InvalidOperationException(SR.MediaContext_InfiniteTickLoop);
                    }

                    _timeManager.Tick();

                    // Although the timing tree is now clean, during layout
                    // more animations may be added, in which case we must tick
                    // again to prevent "first frame" problems. If we do tick
                    // again we want to do so at the same time as before, until
                    // we are done. To that end, lock the tick time to the
                    // first tick. Note that Lock/Unlock aren't counted, so we
                    // can call Lock inside the loop and still safely call
                    // Unlock just once after we are done.
                    _timeManager.LockTickTime();

                    // call all render callbacks
                    FireInvokeOnRenderCallbacks();

                    // signal that the frame has been updated and we are ready to render.

View on GitHub (pinned to 81131a70a4)