{"record":{"id":"5cb16803f2c52bc7","repo":"AvaloniaUI/Avalonia","slug":"visual-was-invalidated-during-the-render-pass","errorCode":null,"errorMessage":"Visual was invalidated during the render pass","messagePattern":"Visual was invalidated during the render pass","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs","lineNumber":88,"sourceCode":"\n    /// <inheritdoc/>\n    public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;\n\n    private void QueueUpdate()\n    {\n        if(_queuedUpdate)\n            return;\n        _queuedUpdate = true;\n        _compositor.RequestCompositionUpdate(_update);\n    }\n    \n    /// <inheritdoc/>\n    public void AddDirty(Visual visual)\n    {\n        if (_isDisposed)\n            return;\n        if (_updating)\n            throw new InvalidOperationException(\"Visual was invalidated during the render pass\");\n        _dirty.Add(visual);\n        QueueUpdate();\n    }\n\n    /// <inheritdoc/>\n    public IEnumerable<Visual> HitTest(Point p, Visual? root, Func<Visual, bool>? filter)\n    {\n        using var _ = Diagnostic.PerformingHitTest();\n\n        CompositionVisual? rootVisual = null;\n        if (root != null)\n        {\n            if (root.CompositionVisual == null)\n                yield break;\n            rootVisual = root.CompositionVisual;\n        }\n        \n        Func<CompositionVisual, bool>? f = null;","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs#L70-L106","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Defer the invalidation: dispatch it after the render pass with Dispatcher.UIThread.Post(() => visual.InvalidateVisual()).","Do not mutate visuals or call InvalidateVisual from inside Render/OnRender — compute state from the current draw context instead.","If invalidation is driven by a timer or async completion, ensure it is posted, not invoked inline."],"exampleFix":"// before\npublic override void Render(DrawingContext ctx)\n{\n    Draw(ctx);\n    InvalidateVisual(); // throws during render pass\n}\n// after\npublic override void Render(DrawingContext ctx)\n{\n    Draw(ctx);\n    Dispatcher.UIThread.Post(InvalidateVisual);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { visual.InvalidateVisual(); }\ncatch (InvalidOperationException) when (rendering) { Dispatcher.UIThread.Post(() => visual.InvalidateVisual()); }","preventionTips":["Never call InvalidateVisual/AddDirty from inside Render/OnRender — post it instead.","Use Dispatcher.UIThread.Post to defer invalidation from render-time handlers.","Keep render callbacks pure: draw only, no tree mutation."],"tags":["rendering","composition","invalid-operation","reentrancy","threading"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}