-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ListView/CollectionView memory leak under Windows #9162
Comments
Thanks for the report and investigation on this! I'm not necessarily the expert on performance testing, but what happens if you try calling the garbage collector? I can imagine that memory might still be growing depending on the memory that is available on the system and only flushed whenever necessary? Besides this repro do you have any real-world scenarios where you had this happening? What made you start investigating this? |
Hi @hbraasch. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time. |
Hi @jfversluis When add the following code to the [Refresh] event, it now tries to force a GC before updating the ImageSource.
The small yellow dots on the memory graph shows where the GC's occurs. They do happen where expected. Unfortunately it still shows the memory growth My real-world application... I'm developing a custom TreeView which inherits from a ListView. It appears to work well when running under Android and iOS (but I must admit I did not check for memory leaks as I do not have the tools - I think). But when I started testing for Windows, the Windows app crashes with an out-of-memory exception. Since VS shows a memory graph while running a Windows app, I quickly noticed the continuous increase in memory each time e.g. a row gets expanded/collapsed, which is done by simply replacing the listview's ItemSource with a newly calculated (but mostly similar sized) one. To investigating this further I started with a simple app (the repro), and found that it shows the same disconcerning effect. I might be barking at the wrong tree, but I though I need to raise this before wasting an enormous amount of time searching for bugs elsewhere. Regards |
I also feel ListView have memory leak, it recycle item? sharex-20220901093408.mp4 |
I add a static int to show count of new itemview, the count still grow. sharex-20220901114011.mp4 |
Change some code of ViewCell, ListView will recycle Cell at Android, but still have bug at Windows, still create new Cell, not only 500. Example source code: |
This bug should be a high priority to fix. The application is literally useless without the ability to scroll through content. And memory leak inside such a structure is a deterrent from even using .NET MAUI to develop anything. |
I too have written a TreeView and also tried composing it using a CollectionView and a ListView and ran into the same problem here: #8151 as well as a layout problem: #4520 |
@Keflon Thanks |
This also happens in a FlexLayout with a BindableLayout.ItemsSource set (see sample attached). |
Can confirm the issue is still here. Application memory usage just keeps going up endlessly as items are added/removed. I worked around it for now by simply having a single ObservableCollection bound to the collectionView and Clear+AddRange from this collection whenever I need to reload the data. |
+1, It really annoys me when it takes up a lot of memory, especially if you add pictures there, the application already takes up more than 1 GB in less than 10 minutes! PS: I also found a temporary solution, as the @feal87 on top wrote this - ObservableCollection - Clear + Add. But it only helps half. |
Same issue here, any update or workaround? |
hello, earlier in the week started using ObservableCollection, which means not needing to reassign ItemsSource constantly, a little better on the memory usage it seems but still the leak is there. also OC has no AddRange function which i kinda need. adding items to OC can only be done 1 at a time, which apparently raises 3 events, this is unnecessary for my needs bc i continually clear and add multiple items. the point is OC does not serve my needs i have no experience with this so this may look silly or not, but i tried today implementing a custom type which inherits from List and INotifyCollectionChanged. i used "public new void" keywords to hide the inherited member and make it use my versions, which really just base version and then raising the CollectionChanged event so that the CollectionView refreshes. i did this for Add and AddRange. works perfectly. as for Clear(), raising the event is not necessary but i noticed that calling Clear() did not do anything to keep memory usage down. what fixed the memory leak issue for me here is to just call
from doing a lot of reading ive been discouraged from forcing a GC.Collect() manually. but here i do it anyway. the result being that now my CollectionView, despite constantly removing and adding items to my custom type as mentioned above, is behaving as i want it to. memory usage only climbs up to a certain point (a few MB) where it then stops increasing. well it's not perfect but it's night and day compared to before i hope this helps others. i have no idea if what i did above is good coding practice (most likely not) but no more (obscene) memory leak. ive been struggling with this issue now for months and was convinced i had to retire my application but now i can continue thank you .NET MAUI team for your hard work, this framework is really nice though it definitely has some issues but for my relatively simple application that i need for some house tasks it is fine for now, i hope everything works out |
Adding/removing items from the collection isn't the problem of this ticket (that is, in fact, the workaround). The problem is related to the way MAUI uses the components underneath (WinUI) to render the collection if the binding changes (you assign a new one). Also you don't need to do a whole new class to have an AddRange. Just inherit from ObservableCollection and add the method
Like this and you're done without triggering multiple events. |
I like your code. I've been using something similar that I found on StackOverflow which does the same thing via extensions without needing to inherit. public static class ObservableCollectionExtensions
{
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
if (collection is null || items?.Any() != true)
{
return;
}
var type = collection.GetType();
if (type.BaseType is null)
{
return;
}
_ = type.InvokeMember("CheckReentrancy", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, collection, null, CultureInfo.InvariantCulture);
var itemsProp = type.BaseType.GetProperty("Items", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
if (itemsProp == null)
{
return;
}
var privateItems = (IList<T>)(itemsProp.GetValue(collection) ?? throw new FieldAccessException("list has no Items"));
foreach (var item in items)
{
privateItems.Add(item);
}
_ = type.InvokeMember(
"OnPropertyChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new PropertyChangedEventArgs("Count") },
CultureInfo.InvariantCulture);
_ = type.InvokeMember(
"OnPropertyChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new PropertyChangedEventArgs("Item[]") },
CultureInfo.InvariantCulture);
_ = type.InvokeMember(
"OnCollectionChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems: items.ToList(), oldItems: Array.Empty<T>()) },
CultureInfo.InvariantCulture);
}
public static void RemoveRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
if (collection is null || items?.Any() != true)
{
return;
}
var type = collection.GetType();
if (type.BaseType is null)
{
return;
}
_ = type.InvokeMember("CheckReentrancy", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, collection, null, CultureInfo.InvariantCulture);
var itemsProp = type.BaseType.GetProperty("Items", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
if (itemsProp == null)
{
return;
}
var privateItems = (IList<T>)(itemsProp.GetValue(collection) ?? throw new FieldAccessException("list has no Items"));
foreach (var item in items)
{
_ = privateItems.Remove(item);
}
_ = type.InvokeMember(
"OnPropertyChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new PropertyChangedEventArgs("Count") },
CultureInfo.InvariantCulture);
_ = type.InvokeMember(
"OnPropertyChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new PropertyChangedEventArgs("Item[]") },
CultureInfo.InvariantCulture);
_ = type.InvokeMember(
"OnCollectionChanged",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
collection,
new object[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems: Array.Empty<T>(), oldItems: items.ToList()) },
CultureInfo.InvariantCulture);
}
} |
This issue does happen with Inserting and Removing items from the ListView (see screenshot below). Unfortunately, this cripples our application where we are presenting a live data stream of the most recent (100) items. When the flow is coming in every 100-50ms, it takes only a mere 30 seconds for the application to become unusable. The simple example below inserts an item to the top of the ListView and removes the bottom-most item from the list. As you can see from the screenshot, there is a strong amount of GC pressure and it never goes down. Even after stopping this test loop (not pictured), the memory is still spiked. If you want this as a separate issue, let me know and I'll report it. public ObservableCollection<string> PacketCache
{
get => _packetCache;
set => SetProperty(ref _packetCache, value);
}
public DelegateCommand<string> CmdTest => new((string _) =>
{
int x = 0;
for (;;)
{
PacketCache.Insert(0, x.ToString());
if (PacketCache.Count >= 100)
PacketCache.RemoveAt(99);
x++;
}
}); <ListView ItemsSource="{Binding PacketCache}"
SelectedItem="{Binding PacketSelected}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<ViewCell>
<Label Text="{Binding .}" FontFamily="Consolas" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView> |
After further investigation for this sample today:
I believe the problems here are now fixed in dotnet/maui/main. Likely #13260 and #14329 specifically. I can see the memory go up by a very small amount, and then goes down: When it goes down, it appears to hit the threshold established in I also tried adding maui/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.cs Lines 295 to 296 in ee6677a
I believe this problem is solved, but I'm always willing to try other sample projects to see if I find anything wrong. You should be able to test these changes in upcoming .NET 8 previews. Thanks! |
@jonathanpeppers Thank you, sir! Always appreciate you taking care of Xamarin/MAUI and providing your magical touch of precision 👍 |
Description
A simple repro (see link below) demonstrates a ListView/CollectionView memory leak under Windows.
Each time the ItemSource gets updated with the exact same list, the app memory usage increases.
The repro is a simplification, normally the ListView/CollectionView gets filled through a binding. However the repro still demonstrates the same problem.
See this video for a demonstration:
ListviewMemoryTester.mp4
I'm a VS Community user so do not have access to the Xamarin Profiler to see if the problem also exists in Android and iOS.
Steps to Reproduce
Version with bug
6.0.408 (current)
Last version that worked well
Unknown/Other
Affected platforms
Windows
Affected platform versions
net6.0-windows10.0.19041.0
Did you find any workaround?
No
Relevant log output
No response
The text was updated successfully, but these errors were encountered: