AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.06k stars 2.25k forks source link

Memory leak when XAML binding to parent control in DataTemplate #3335

Open balthild opened 4 years ago

balthild commented 4 years ago

Demo: https://github.com/balthild/avalonia-memory-leak-example

Binding to parent control in DataTemplate of ListBox creates objects that cannot be GC'ed. It seems that if a lot of items are shown in a ListBox and then removed, the items are not recycled.

If I change the binding {Binding #MainWindow.Title} to {Binding $parent[Window].Title}, the memory leak will disappear.

MainWindow.xaml

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:AvaloniaApplication1.ViewModels;assembly=AvaloniaApplication1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Class="AvaloniaApplication1.Views.MainWindow"
        Width="300" Height="600"
        WindowStartupLocation="CenterScreen"
        Name="MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaApplication1">

    <Design.DataContext>
        <vm:MainWindowViewModel />
    </Design.DataContext>

    <StackPanel Orientation="Vertical" Spacing="8" Margin="8">
        <Button Command="{Binding RefreshList}" Content="Refresh" />

        <ListBox Items="{Binding LeakedItems}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Spacing="8" Orientation="Horizontal">
                        <TextBlock Text="{Binding #MainWindow.Title}" />
                        <TextBlock Text="{Binding}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

</Window>

MainWindowViewModel.cs

using System.Linq;
using ReactiveUI;

namespace AvaloniaApplication1.ViewModels {
    public class MainWindowViewModel : ViewModelBase {
        private string[] leakedItems = { };

        public string[] LeakedItems {
            get => leakedItems;
            set => this.RaiseAndSetIfChanged(ref leakedItems, value);
        }

        private bool aLot = true;

        public void RefreshList() {
            int count = aLot ? 50 : 1;
            aLot = !aLot;

            LeakedItems = Enumerable.Range(0, count)
                .Select(_ => "Test Test Test")
                .ToArray();
        }
    }
}

图片

aguahombre commented 4 years ago

I've also run into this problem.

Could it be that NeverEndingSynchronousCompletionAsyncResultObservable.Subscribe doesn't set _asyncResult to null on the async path through the code?

            public IDisposable Subscribe(IObserver<T> observer)
            {
                if (_asyncResult?.IsCompleted == true)
                {
                    _value = _asyncResult.Value.GetResult();
                    _asyncResult = null;
                }

                if (_asyncResult != null)
                    _asyncResult.Value.OnCompleted(() =>
                    {
                        observer.OnNext(_asyncResult.Value.GetResult());
// _asyncResult = null; // would adding this fix the memory leak?
                    });
                else
                    observer.OnNext(_value);
// _value= null; // would adding this fix the memory leak?   

                return Disposable.Empty;
            }
kekekeks commented 4 years ago

@aguahombre I think that code is only referencing the Window from the ListBox item, not the other way around, so it's probably not related to the issue