maui icon indicating copy to clipboard operation
maui copied to clipboard

Weak subscription to CanExecuteChange events

Open PureWeen opened this issue 5 months ago • 2 comments
trafficstars

Description of Change

This pull request introduces a mechanism for managing and cleaning up command event subscriptions when the lifetime of the ICommand instance exceeds that of the XAML element it's assigned to.

Example from the Unit Tests

var command = new CommandWithoutWeakEventHandler();

// The Button subscribes to "command.CanExecuteChanged", creating a strong reference
// from the command to the Button. If the command outlives the Button,
// this prevents the Button from being garbage collected, resulting in a memory leak.
// Our built-in Command implementation avoids this by using WeakEventManager,
// but custom ICommand implementations that do not use weak event patterns are prone to leaks.
{
	var button = new Button();
	button.Command = command;
}			

This largerly comes up with scenarios using static viewmodels or if people are using reference bindings. The key changes include the addition of a WeakCommandSubscription class, updates to the ICommandElement interface, and new unit tests to verify the functionality.

Memory Management Enhancements:

  • Added WeakCommandSubscription class: Introduced a new class to manage weak references to command subscriptions, ensuring that event handlers do not prevent garbage collection of associated objects. This includes the CommandCanExecuteSubscription inner class for handling CanExecuteChanged events. (src/Controls/src/Core/WeakCommandSubscription.cs)

  • Updated ICommandElement interface: Added a CleanupTracker property to manage the lifecycle of command subscriptions. (src/Controls/src/Core/ICommandElement.cs)

  • Integrated WeakCommandSubscription in controls: Implemented the CleanupTracker property in multiple controls (Button, ImageButton, MenuItem, RefreshView, SearchBar) to utilize the new subscription management system. [1] [2] [3] [4] [5]

Command Handling Improvements:

  • Modified CommandElement static class: Updated OnCommandChanging and OnCommandChanged methods to use the CleanupTracker for disposing of old subscriptions and creating new ones. (src/Controls/src/Core/CommandElement.cs)

Unit Testing Enhancements:

  • New tests for garbage collection: Added tests to ensure that controls with commands can be garbage collected properly and that weak event handlers are not collected prematurely. (src/Controls/tests/Core.UnitTests/ButtonUnitTest.cs)

  • Improved garbage collection helper: Enhanced the TestHelpers.Collect method to force multiple garbage collection cycles and ensure deterministic behavior during testing. (src/Controls/tests/Core.UnitTests/TestHelpers.cs)

These changes collectively improve the robustness of command handling in the library while addressing potential memory management issues.

Concerns

  • Calling Managed code from the finalizer typically isn't recommended. The only purpose of the finalizer is to cleanup CommandCanExecuteSubscription. The commandelement itself will cleanup without the finalizer. One approach we could take here would be to track these and then just clean them up every so often.

Other Notes

  • WPF has a much larger solution to this https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/CanExecuteChangedEventManager.cs#L321

Issues Fixed

Fixes #16124

PureWeen avatar Jun 05 '25 14:06 PureWeen

/rebase

PureWeen avatar Jun 12 '25 12:06 PureWeen

I've been testing this PR and it solved a lot of leaks.

It's a hit!

Thank you guys

Transis-Pedro avatar Jun 15 '25 00:06 Transis-Pedro