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

如何无内存泄漏地存储树形节点的历史记录?

内存泄漏问题:树形节点历史记录存储优化

我有一个类树形字典结构,运行时内存从初始100MB快速增长至3GB且无法自动释放。经排查核心问题出在每个节点的历史记录模块——移除if(level >= topic.Length)代码块内的所有逻辑后,内存可正常释放。现咨询如何在无内存泄漏的前提下存储每个树形节点的历史记录,相关代码如下:

树形节点类代码

public class TreeNode
{
    protected TreeNode(string part) : this(part, -1, null) {}
    
    private TreeNode(string part, int level, TreeNode? parent)
    {
        Part = part;
        Level = level;
        Parent = parent;
        Root = parent?.Root ?? this;
        
        if(parent!=null)
          MessagesCount = 1;
    }
    private int _historyId;
    public int Level { get; }
    public TreeNode? Parent { get; }
    public TreeNode Root { get; }
    [Reactive] public string Part {get;set;}
    [Reactive] public string? Header {get;set;}
    [Reactive] public string? Payload {get;set;}
    [Reactive] public string? Topic {get;set;}
    [Reactive] public MqttDelivery? Delivery {get;set;}
    [Reactive] public int MessagesCount {get;set;}
    [Reactive] public int TopicsCount {get;set;}

    public ObservableCollection<ReceivedHistoryMessage> UiHistoryMessages { get; set; } = new();
    public readonly Dictionary<string, TreeNode> _treeChildren = new();

    public bool Add(
      string[] subTopic,
      MqttDelivery delivery)
      => Add(subTopic, Level + 1, delivery);

    private bool Add(
      string[] topic,
      int level,
      MqttDelivery delivery)
    {
        if(level >= topic.Length)
        {
            Topic = delivery.Topic;
            Header = delivery.Header?.ToJsonString(true);
            Payload = delivery.Payload?.ToJsonString(true);
            Delivery = delivery;
           
            AddToHistory(delivery);
            return false;
        }

        var part = topic[level];
        if(!_treeChildren.TryGetValue(part, out var trie))
        {
            TopicCount++;
            _treeChildren.Add(part, trie = new TreeNode(part, level, this));
        }

        MessagesCount++;
        trie.Add(topic, level+1, delivery);

        return true;
    }

    private void AddToHistory(MqttDelivery mqttMessage)
    {
       while(UiHistoryMessages.Count >= 30)
          UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1);

       UiHistoryMessages.Insert(0, new ReceivedHistoryMessage()
       {
          Id = _historyId++,
          Time = DateTime.Now,
          Message = mqttMessage,
          UIFormattedMessage = $"{DateTime.Now:mm:ss}, {mqttMessage.Header.ToJsonString()}"
        
       });
    }
}

历史消息及MQTT对象定义

public class ReceivedHistoryMessage
{
    public int Id { get; set; }
    public string? UIFormattedMessage { get; set; }
    public DateTime Time { get; set; }
    public MqttMessage? Message { get; set; }
}

[Name( "message" )]
public partial class MqttMessage
{
    public MqttMessageHeader Header { get; set; } =
        new();

    public JObject Payload { get; set; }
}

核心问题分析

当前内存泄漏的根源:

  • ObservableCollection<ReceivedHistoryMessage>对MqttDelivery/MqttMessage持有强引用,而树形节点本身长期存活,导致历史消息无法被GC回收
  • 即使移除旧历史项,若UI绑定未正确解除,ObservableCollection的项仍可能被UI上下文持有,无法释放
  • 树形节点自身已存储Delivery、Header等数据,历史记录重复存储完整大对象,加剧内存占用

无泄漏优化方案

1. 弱引用存储历史消息

将历史消息中的原始MQTT对象改为弱引用,避免强引用阻碍GC回收:

public class ReceivedHistoryMessage
{
    public int Id { get; set; }
    public string? UIFormattedMessage { get; set; }
    public DateTime Time { get; set; }
    // 用弱引用存储原始消息,不强制持有对象
    public WeakReference<MqttMessage>? MessageRef { get; set; }

    // 可选:提供安全获取消息的方法
    public MqttMessage? GetMessage()
    {
        MessageRef?.TryGetTarget(out var msg);
        return msg;
    }
}

// 修改AddToHistory方法
private void AddToHistory(MqttDelivery mqttMessage)
{
    while(UiHistoryMessages.Count >= 30)
        UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1);

    UiHistoryMessages.Insert(0, new ReceivedHistoryMessage()
    {
        Id = _historyId++,
        Time = DateTime.Now,
        MessageRef = new WeakReference<MqttMessage>(mqttMessage.Message),
        UIFormattedMessage = $"{DateTime.Now:mm:ss}, {mqttMessage.Header.ToJsonString()}"
    });
}

2. 优化集合类型或手动清理

如果UI不需要实时响应集合变化,用List<ReceivedHistoryMessage>替代ObservableCollection(后者会维护额外事件订阅,易引发泄漏):

// 替换原ObservableCollection
public List<ReceivedHistoryMessage> UiHistoryMessages { get; set; } = new();

若必须保留ObservableCollection,需给TreeNode添加清理方法,在节点废弃时主动释放资源:

public void Cleanup()
{
    UiHistoryMessages.Clear();
    // 递归清理子节点
    foreach(var child in _treeChildren.Values)
    {
        child.Cleanup();
    }
    _treeChildren.Clear();
}

3. 避免重复存储大对象

利用树形节点已有的Header、Topic等数据,历史记录只存储UI展示必要的信息,不持有完整MQTT对象:

public class ReceivedHistoryMessage
{
    public int Id { get; set; }
    public string? UIFormattedMessage { get; set; }
    public DateTime Time { get; set; }
    public string? Topic { get; set; }
    public string? HeaderJson { get; set; }
}

// 修改AddToHistory方法
private void AddToHistory(MqttDelivery mqttMessage)
{
    while(UiHistoryMessages.Count >= 30)
        UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1);

    var headerJson = mqttMessage.Header?.ToJsonString();
    UiHistoryMessages.Insert(0, new ReceivedHistoryMessage()
    {
        Id = _historyId++,
        Time = DateTime.Now,
        Topic = mqttMessage.Topic,
        HeaderJson = headerJson,
        UIFormattedMessage = $"{DateTime.Now:mm:ss}, {headerJson}"
    });
}

4. 检查Reactive属性订阅

代码中使用的[Reactive]属性会生成订阅者列表,需确保所有订阅节点属性变化的代码,在不需要时调用Dispose取消订阅,避免残留引用导致泄漏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:44:54