Fixes FluidMoveBehavior static dictionary memory leaks - #198
Merged
Conversation
…ionStoryboardDictionary Replace strong FrameworkElement references in TagData with WeakReference<FrameworkElement> to prevent elements removed from the visual tree from being retained indefinitely. - TagData.Child/Parent now use WeakReference-backed properties with unchanged API - Remove unreliable timestamp-based purge; replace with throttled liveness check that detects dead WeakReferences and unloaded element-type keys - Add PurgeDeadStoryboards to clean TransitionStoryboardDictionary for both element-type and DataContext-type keys - Add OnDetaching to FluidMoveBehavior to stop/remove active storyboards on detach - Null-guard tagData.Child reads in UpdateLayoutTransitionCore (cache in local var) - Add unit tests for WeakReference behavior, dictionary purge, and storyboard cleanup Fixes #38
Brian Lagunas (brianlagunas)
requested review from
Marco Goertz (mgoertz-msft) and
Peter Spada (spadapet)
April 3, 2026 01:18
Peter Spada (spadapet)
approved these changes
Apr 3, 2026
Peter Spada (spadapet)
approved these changes
Apr 6, 2026
This was referenced Sep 2, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Change
Addresses memory leaks in
FluidMoveBehavior's staticTagDictionaryandTransitionStoryboardDictionary. Previously, these dictionaries could hold strong references toUIElementsandStoryboardseven after the associated elements were removed from the visual tree or garbage collected, leading to memory accumulation over time.This change introduces several improvements:
TagDatastructure now usesWeakReferenceforChildandParentproperties, allowing elements to be garbage collected when no other strong references exist.OnLayoutUpdatedto regularly clean upTagDictionaryentries whose associated child elements are no longer alive or are unloaded from the visual tree.PurgeDeadStoryboardsmethod to proactively removeStoryboardentries fromTransitionStoryboardDictionaryif their correspondingFrameworkElementkey is unloaded, if the associatedTagData's child is dead, or if theTagDictionaryentry is missing.UpdateTagDataandUpdateLayoutTransitionCoreto prevent re-adding already-collected elements.Storyboardsare explicitly stopped and removed when aFluidMoveBehavioris detached or its tracked children are removed from the panel.FluidMoveBehaviorTest.cs, to validate the weak reference behavior, dictionary purging, and overall retention logic in various scenarios (element removal, reparenting).Note on ConditionalWeakTable: I considered replacing the static TagDictionary with ConditionalWeakTable to eliminate strong-key retention, but I don’t think it is a safe drop-in change here. While it works well for reference-identity keys like FrameworkElement and some DataContext cases, FluidMoveBehavior also supports TagPath, which means tags can be scalar values or strings and currently rely on normal Dictionary<object, ...> equality semantics (Equals / GetHashCode). ConditionalWeakTable uses reference identity, not value equality, so switching to it could break matching behavior for TagPath-based tags even when the values are logically equal. It also would not help much with transitionStoryboardDictionary, which still needs explicit enumeration and cleanup for stopping storyboards and tearing down overlay state. Because of that, I think ConditionalWeakTable is appealing for leak prevention but too risky here unless the behavior is redesigned around reference-identity-only tags.
Bugs Fixed
FluidMoveBehaviorwhere static dictionaries retained references to UI elements and associated storyboards after they were no longer active.API Changes
Added:
TagData.IsAliveproperty (internal)FluidMoveBehaviorBase.ResetPurgeThrottle()method (internal)FluidMoveBehavior.PurgeDeadStoryboards()method (internal)FluidMoveBehavior.InjectStoryboardEntry()method (internal)FluidMoveBehavior.StoryboardDictionaryContainsKey()method (internal)FluidMoveBehavior.ClearStoryboardDictionary()method (internal)Changed:
TagData.ChildandTagData.Parentnow useWeakReferenceinternally.FluidMoveBehavior.OnDetaching()behavior to include storyboard cleanup.FluidMoveBehaviorBase.OnLayoutUpdated.Removed:
TagData.Timestampproperty.Behavioral Changes
Applications using
FluidMoveBehaviorwill experience improved memory management, particularly in scenarios involving dynamic UI where elements are frequently added, removed, or reparented. Elements and their associatedStoryboardswill now be correctly garbage collected, preventing a steady increase in memory footprint that could occur under previous implementations.PR Checklist
FluidMoveBehaviorTest.csfile provides extensive unit tests)