Как уменьшить потребление памяти во время постоянного обновления дерева?
У меня есть класс TreeNode, который отвечает за ноду, выглядит вот так:
public class TreeNode
: ViewModelBase
{
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;
}
#region IO
private bool _updateEnqueued;
public int Level { get; }
public TreeNode? Parent { get; }
public TreeNode Root { get; }
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
this.RaiseAndSetIfChanged(ref _isExpanded, value);
Root.EnqueueUpdate();
}
}
private AvaloniaList<TreeNode> _visibleChildren = new();
public IAvaloniaReadOnlyList<TreeNode> VisibleChildren
=> _visibleChildren;
public readonly Dictionary<string, TreeNode> _treeChildren = new();
public ObservableCollection<ReceivedHistoryMessage> UiHistoryMessages { get; set; } = new();
#endregion
public bool Add(
string[] subTopic,
MqttDelivery delivery)
=> Add(subTopic, Level + 1, delivery);
public void ForceResync()
=> Root.Update();
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.ContainsKey(part))
{
TopicsCount++;
_treeChildren.Add(part, new TreeNode(part, level, this));
Root.EnqueueUpdate(); // обновление ноды
}
MessagesCount++;
_treeChildren[ part ].Add(topic, level + 1, delivery);
return true;
}
private void EnqueueUpdate()
{
if (_updateEnqueued)
return;
_updateEnqueued = true;
Dispatcher.UIThread.Post(
Update,
DispatcherPriority.Background);
}
private void AppendItems()
{
_updateEnqueued = false;
var flatTreeList = new AvaloniaList<TreeNode>();
AppendItems(flatTreeList, this);
_visibleChildren = flatTreeList;
}
private void AppendItems(
AvaloniaList<TreeNode> flatTreeList,
TreeNode treeNode )
{
flatTreeList.Add(treeNode);
if (!treeNode.IsExpanded)
return;
foreach (var ch in treeNode._treeChildren)
AppendItems(flatTreeList, ch.Value);
}
private void Update()
{
AppendItems();
Root.RaisePropertyChanged(nameof(Root.VisibleChildren));
}
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:hh:mm:ss},
{mqttMessage.Header.ToJsonString()}"
} );
}
}
Раньше я обновлял дерево по клику на элемент дерева - нажал, раскрылось, обновилось (set в свойстве IsExpanded) и все хорошо, но появилось предложение обновлять постоянно, т.к. можно не нажать и не увидеть чего-то нового. Я добавил обновление еще и в метод Add, но появилась проблема - память слишком быстро тратится и я не знаю, что с этим можно сделать. Расстраивает то, что память не контролируется - полный день работы приложения и минус пару гб оперативной памяти, а хотелось бы поддерживать какое-то постоянное значение, желательно в рамках 150-200 мб, что и было раньше, до добавления обновления ноды в метод Add. Подскажите, пожалуйста, как лучше сделать? Как бы вы сделали?
upd:
У меня есть класс ConnectionLogic, в котором объявлено вот такое свойство:
public TreeRoot TreeRoot { get; }
Логика следующая: мне шина присылает сообщения, на каждое сообщение я вызываю метод Add и выглядит это так:
var subTopic = mqttDelivery.Topic.Split('/');
TreeRoot.Add(
subTopic: subTopic,
delivery: mqttDelivery);
Что происходит в методе: в словарь добавляются элементы, они же ветви дерева, одновременно все это рисуется на ui. Будем считать, что ui - это wpf, где есть биндинги. Поэтому на UI показываются элементы коллекции VisibleChildren
. А добавляются элементы в эту коллекцию методом AppendItems
, в котором происходит проход по словарю (ранее используется в методе Add
)
Обновление же коллекции вызывает метод Update
, в котором и происходят аллокации (обратите внимание на Root.RaisePropertyChanged(...)
)
Проблема прям на поверхности: расходуется много памяти, а главное - это не контролируется. Клик по дереву вызывает метод Update
, добавление в дерево вызывает метод Update
. Все это и приводит к утечкам.
У меня такое чувство, будто память вообще не очищается - никогда не замечал, чтобы уменьшалось потребляемое кол-во, только с каждым кликом и добавлением в дерево увеличивается и увеличивается потребление