AvaloniaUI/Avalonia · error · InvalidOperationException

Visual was invalidated during the render pass

Error message

Visual was invalidated during the render pass

What it means

Thrown by CompositingRenderer.AddDirty when a visual is marked dirty while the renderer is mid-render (_updating is true). Mutating the dirty set during the render pass would corrupt the iteration/update, so it is treated as an illegal re-entrancy (InvalidOperationException).

Source

Thrown at src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs:88

    /// <inheritdoc/>
    public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;

    private void QueueUpdate()
    {
        if(_queuedUpdate)
            return;
        _queuedUpdate = true;
        _compositor.RequestCompositionUpdate(_update);
    }
    
    /// <inheritdoc/>
    public void AddDirty(Visual visual)
    {
        if (_isDisposed)
            return;
        if (_updating)
            throw new InvalidOperationException("Visual was invalidated during the render pass");
        _dirty.Add(visual);
        QueueUpdate();
    }

    /// <inheritdoc/>
    public IEnumerable<Visual> HitTest(Point p, Visual? root, Func<Visual, bool>? filter)
    {
        using var _ = Diagnostic.PerformingHitTest();

        CompositionVisual? rootVisual = null;
        if (root != null)
        {
            if (root.CompositionVisual == null)
                yield break;
            rootVisual = root.CompositionVisual;
        }
        
        Func<CompositionVisual, bool>? f = null;

View on GitHub (pinned to 11c5427268)

Solutions

  1. Defer the invalidation: dispatch it after the render pass with Dispatcher.UIThread.Post(() => visual.InvalidateVisual()).
  2. Do not mutate visuals or call InvalidateVisual from inside Render/OnRender — compute state from the current draw context instead.
  3. If invalidation is driven by a timer or async completion, ensure it is posted, not invoked inline.

Example fix

// before
public override void Render(DrawingContext ctx)
{
    Draw(ctx);
    InvalidateVisual(); // throws during render pass
}
// after
public override void Render(DrawingContext ctx)
{
    Draw(ctx);
    Dispatcher.UIThread.Post(InvalidateVisual);
Defensive patterns

Strategy: try-catch

Try / catch

try { visual.InvalidateVisual(); }
catch (InvalidOperationException) when (rendering) { Dispatcher.UIThread.Post(() => visual.InvalidateVisual()); }

Prevention

When it happens

Trigger: Calling visual.InvalidateVisual() (or anything that calls AddDirty) from within a render callback, a RenderOverride, or a property changed handler that fires synchronously during the composition update pass.

Common situations: A custom rendered control invalidates itself inside its own Render/OnRender; a property change during layout triggers an invalidate that runs during the update; background work marshals an invalidate onto the render thread at the wrong time.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/5cb16803f2c52bc7. Report an issue: GitHub.