.Net 8/ 9 Memory leaks #25931
Replies: 13 comments
-
We've found some similar issues:
If any of the above are duplicates, please consider closing this issue out and adding additional context in the original issue.
|
Beta Was this translation helpful? Give feedback.
-
Hi we have the same issues and despite what we do we have memory leaks - I would expect my pages marked as transient to be cleaned up. |
Beta Was this translation helpful? Give feedback.
-
Are you also using I am not a fan of DI, in general, so I might not be best to comment about its usage. 😄 |
Beta Was this translation helpful? Give feedback.
-
@jonathanpeppers Yes I am using DI and I am using Idisposable on those transient pages but they get not cleaned up either. |
Beta Was this translation helpful? Give feedback.
-
I think the current behavior is that transient |
Beta Was this translation helpful? Give feedback.
-
Yea, that's how |
Beta Was this translation helpful? Give feedback.
-
Is this a nice simple workaround or a terrible idea? public App(IServiceProvider serviceProvider)
{
InitializeComponent();
var loginPage = serviceProvider.GetRequiredService<LoginPage>();
MainPage = new NavigationPage(loginPage);
((NavigationPage)MainPage).Popped += PagePopped;
}
private void PagePopped(object? sender, NavigationEventArgs e)
{
(e.Page as IDisposable)?.Dispose();
} |
Beta Was this translation helpful? Give feedback.
-
This is something that depends on your scenario... if your pages are transient that should work... Or maybe using the In general your approach looks good, just remember to unsubscribe the event, since you're subscribing it from App class, and it will live forever it can prevent your page to be garbage collected |
Beta Was this translation helpful? Give feedback.
-
@pictos thanks for the feedback. Yes, all the pages are registered via AddTransient() in the DI container. A good reminder to -= the |
Beta Was this translation helpful? Give feedback.
-
The DI guidelines says this about disposal of services: The container is responsible for cleanup of types it creates, and calls Dispose on IDisposable instances. Following this guidance, I found that creating a new IServiceScope for resolution of the pushed pages, and then popping and disposing the IServiceScope instance when the page was popped yielded the desired results. Because this approach follows the guidance, this feels legit. So, maybe this isn't a bug at all? Note here that I'm only managing the scope for the NavigationStack, but I could easily do the same for the ModalStack (for disposal of modal pages). public class NavigationService : INavigationService
{
INavigation StackNavigation => Microsoft.Maui.Controls.Application.Current!.MainPage!.Navigation;
// View model to view lookup - making the assumption that view model to view will always be 1:1
private readonly Dictionary<Type, Type> _viewModelViewDictionary = new Dictionary<Type, Type>();
private readonly IServiceProvider serviceProvider;
private readonly Stack<IServiceScope> serviceScopeStack = new Stack<IServiceScope>();
public NavigationService(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public void Register<TBaseViewModel, TBaseContentPage>()
where TBaseViewModel : BaseViewModel
where TBaseContentPage : BaseContentPage
{
if (!_viewModelViewDictionary.ContainsKey(typeof(TBaseViewModel)))
{
_viewModelViewDictionary.Add(typeof(TBaseViewModel), typeof(TBaseContentPage));
}
}
public async Task PopAsync(bool animate = false)
{
var page = await StackNavigation.PopAsync(animate);
var scope = serviceScopeStack.Pop();
scope.Dispose();
}
public async Task PopToRootAsync(bool animate = false)
{
await StackNavigation.PopToRootAsync(animate);
var scopeToPopAndDispose = serviceScopeStack.Pop();
do
{
scopeToPopAndDispose.Dispose();
scopeToPopAndDispose = serviceScopeStack.Pop();
}
while(scopeToPopAndDispose != null);
}
public Task PushAsync<TViewModel>(bool animate = false, Action<TViewModel>? initAction = null) where TViewModel : BaseViewModel
{
var viewType = _viewModelViewDictionary[typeof(TViewModel)];
var scope = serviceProvider.CreateScope();
var page = (BaseContentPage<TViewModel>)scope.ServiceProvider.GetRequiredService(viewType);
serviceScopeStack.Push(scope);
initAction?.Invoke(page.ViewModel);
return StackNavigation.PushAsync(page, animate);
}
} |
Beta Was this translation helpful? Give feedback.
-
If it is transient, it should be release and the DI container won't hold it. But, maybe this is not the page but the handler holding the page? That is a very narrow hot path. Anything else in there that looks like 5 or 10 or something? |
Beta Was this translation helpful? Give feedback.
-
This probably won't help. The DI container is still going to retain a reference to Page because your Pages implement Unless there's some specific reason you need to use If you need logic to run when a page is popped then I'd just add some method to call "NavigatedAway" or "Popped" and call that, or look at using |
Beta Was this translation helpful? Give feedback.
-
This is the more correct approach. The one caveat here is that if you start trying to resolve MAUI types you might run into issues. For example, we register the We'd like to implement what you have here as a feature of MAUI at some point but just haven't quite had time to unfortunately. @albyrock87 has a helpful navigation library here that probably does a lot of what you're looking for. |
Beta Was this translation helpful? Give feedback.
-
Description
We are having some memory issues in our app where the app gets slower after some time and noticed in a gcdump file that all our pages have multiple counts.
We created a dummy project with a second transient page, and added a buton with a method to go back, and call dispose method as well.
However in the gcdump there are still multiple counts of this page.
Our question is how are transient pages managed in .NET MAUI?
We tested in .net 8 and 9 Transient View/ViewModels are not cleaned up.
How do static variables affect memory management in .NET MAUI?
If I am injecting a Singleton Service into a ViewModel will this be cleaned up or also the service has to be transient?
This is a basic default project with no viewmodels and minimal dependency injection, so what is causing the page to be kept in memory?
Steps to Reproduce
Link to public reproduction project repository
https://github.com/Zack-G-I-T/MauiMemoryIssue
Version with bug
9.0.0-rc.2.24503.2
Is this a regression from previous behavior?
No, this is something new
Last version that worked well
Unknown/Other
Affected platforms
Android
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
No response
Beta Was this translation helpful? Give feedback.
All reactions