.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
已尝试方案
- 实现IDisposable模式
- 在释放时清除ItemsSource
- 正确取消事件订阅
- 通过MAX_MESSAGES限制集合大小
- 使用MainThread.BeginInvokeOnMainThread更新UI
预期表现
- ListView应保持稳定的内存占用
- 从ObservableCollection中移除项时内存应被正确清理
问题原因及解决方案
核心原因
- ListView单元格缓存泄漏:.NET MAUI的ListView默认会缓存已创建的ViewCell,即使数据源项被移除,缓存的视图可能仍持有对数据源对象或页面的强引用,导致GC无法回收这些对象。
- DispatcherTimer强引用残留:虽然已取消Tick事件订阅,但DispatcherTimer本身可能被UI线程调度队列持有强引用,导致页面实例无法被及时回收。
- 闭包引用泄漏:
Timer_Tick中的闭包捕获了messages和页面实例,即使消息被移除,闭包的引用链仍可能未切断,阻止GC回收相关对象。
具体解决措施
- 清理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; } }
- 彻底释放DispatcherTimer
优化定时器清理逻辑,添加显式释放操作:
private void StopAndDisposeTimer() { if (timer != null) { timer.Stop(); timer.Tick -= Timer_Tick; // 显式释放定时器资源 if (timer is IDisposable disposableTimer) { disposableTimer.Dispose(); } timer = null; } }
- 使用弱引用避免闭包泄漏
在定时器回调中用弱引用访问集合,切断闭包对页面的强引用:
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); } } }); } }
- 验证GC回收情况
测试时可手动触发GC,排除GC延迟干扰:
// 在页面Dispose或测试按钮点击时调用 GC.Collect(); GC.WaitForPendingFinalizers();
内容的提问来源于stack exchange,提问作者sssyyy
相关产品推荐
相关产品推荐

