You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中ListView内存持续增长问题求助

为何在.NET MAUI中使用ListView时内存占用会持续增长?

实际表现

  • 内存占用随时间持续增长
  • 即使已实现正确的释放模式,内存仍未得到释放

代码实现

using System.Collections.ObjectModel;

namespace MauiListView
{
    public class Message
    {
        public string? Content { get; set; }
        public DateTime Timestamp { get; set; }
    }

    public partial class MainPage : ContentPage, IDisposable
    {
        private const int MAX_MESSAGES = 10;
        private readonly ObservableCollection<Message> messages;
        private IDispatcherTimer? timer;
        private bool isDisposed;

        public MainPage()
        {
            InitializeComponent();
            messages = new ObservableCollection<Message>();
            MessageListView.ItemsSource = messages;
        }

        private void StartMessageGeneration()
        {
            // 清理之前的定时器
            StopAndDisposeTimer();

            timer = Application.Current?.Dispatcher.CreateTimer();
            if (timer != null)
            {
                timer.Interval = TimeSpan.FromSeconds(1);
                timer.Tick += Timer_Tick;
                timer.Start();
            }
        }

        private void StopAndDisposeTimer()
        {
            if (timer != null)
            {
                timer.Stop();
                timer.Tick -= Timer_Tick; // 取消事件订阅
                timer = null;
            }
        }

        private void Timer_Tick(object? sender, EventArgs e)
        {
            if (messages == null) return;

            var message = new Message
            {
                Content = $"新消息 {DateTime.Now}",
                Timestamp = DateTime.Now
            };

            MainThread.BeginInvokeOnMainThread(() =>
            {
                messages.Insert(0, message);
                while (messages.Count > MAX_MESSAGES)
                {
                    messages.RemoveAt(messages.Count - 1);
                }
            });
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    StopAndDisposeTimer();
                    messages.Clear();
                    MessageListView.ItemsSource = null;
                }
                isDisposed = true;
            }
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            StartMessageGeneration();
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            StopAndDisposeTimer();
        }

        ~MainPage()
        {
            Dispose(false);
        }
    }
}

环境信息

  • .NET MAUI版本:8.0
  • 测试平台:Windows

已尝试方案

  1. 实现IDisposable模式
  2. 在释放时清除ItemsSource
  3. 正确取消事件订阅
  4. 通过MAX_MESSAGES限制集合大小
  5. 使用MainThread.BeginInvokeOnMainThread更新UI

预期表现

  • ListView应保持稳定的内存占用
  • 从ObservableCollection中移除项时内存应被正确清理

问题原因及解决方案

核心原因

  1. ListView单元格缓存泄漏:.NET MAUI的ListView默认会缓存已创建的ViewCell,即使数据源项被移除,缓存的视图可能仍持有对数据源对象或页面的强引用,导致GC无法回收这些对象。
  2. DispatcherTimer强引用残留:虽然已取消Tick事件订阅,但DispatcherTimer本身可能被UI线程调度队列持有强引用,导致页面实例无法被及时回收。
  3. 闭包引用泄漏:Timer_Tick中的闭包捕获了messages和页面实例,即使消息被移除,闭包的引用链仍可能未切断,阻止GC回收相关对象。

具体解决措施

  1. 清理ListView单元格缓存
    在Dispose方法中通过反射清理ListView的内部缓存(针对.NET MAUI 8.0 Windows平台):
protected virtual void Dispose(bool disposing)
{
    if (!isDisposed)
    {
        if (disposing)
        {
            StopAndDisposeTimer();
            messages.Clear();
            MessageListView.ItemsSource = null;
            
            // 清理ListView单元格缓存
            if (MessageListView is ListView listView)
            {
                var cacheProperty = listView.GetType().GetProperty("CellCache", 
                    System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                cacheProperty?.SetValue(listView, null);
            }
        }
        isDisposed = true;
    }
}
  1. 彻底释放DispatcherTimer
    优化定时器清理逻辑,添加显式释放操作:
private void StopAndDisposeTimer()
{
    if (timer != null)
    {
        timer.Stop();
        timer.Tick -= Timer_Tick;
        // 显式释放定时器资源
        if (timer is IDisposable disposableTimer)
        {
            disposableTimer.Dispose();
        }
        timer = null;
    }
}
  1. 使用弱引用避免闭包泄漏
    在定时器回调中用弱引用访问集合,切断闭包对页面的强引用:
private void Timer_Tick(object? sender, EventArgs e)
{
    var weakMessages = new WeakReference<ObservableCollection<Message>>(messages);
    if (weakMessages.TryGetTarget(out var targetMessages))
    {
        var message = new Message
        {
            Content = $"新消息 {DateTime.Now}",
            Timestamp = DateTime.Now
        };

        MainThread.BeginInvokeOnMainThread(() =>
        {
            if (weakMessages.TryGetTarget(out var currentMessages))
            {
                currentMessages.Insert(0, message);
                while (currentMessages.Count > MAX_MESSAGES)
                {
                    currentMessages.RemoveAt(currentMessages.Count - 1);
                }
            }
        });
    }
}
  1. 验证GC回收情况
    测试时可手动触发GC,排除GC延迟干扰:
// 在页面Dispose或测试按钮点击时调用
GC.Collect();
GC.WaitForPendingFinalizers();

内容的提问来源于stack exchange,提问作者sssyyy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:42:16