Limit MsgView log queue growth to reduce memory usage

This commit is contained in:
xpzhxhm 2026-03-14 22:37:20 +00:00
parent 214a09bc48
commit d186b1d8e9
3 changed files with 120 additions and 31 deletions

View file

@ -2,9 +2,15 @@ namespace ServiceLib.ViewModels;
public class MsgViewModel : MyReactiveObject public class MsgViewModel : MyReactiveObject
{ {
private const int MaxQueuedMessagesVisible = 2_000;
private const int MaxQueuedMessagesHidden = 500;
private const int MaxFlushMessages = 200;
private const int MaxFlushChars = 64 * 1024;
private readonly ConcurrentQueue<string> _queueMsg = new(); private readonly ConcurrentQueue<string> _queueMsg = new();
private volatile bool _lastMsgFilterNotAvailable; private volatile bool _lastMsgFilterNotAvailable;
private int _queuedMessageCount = 0;
private int _showLock = 0; // 0 = unlocked, 1 = locked private int _showLock = 0; // 0 = unlocked, 1 = locked
private long _droppedMessageCount = 0;
public int NumMaxMsg { get; } = 500; public int NumMaxMsg { get; } = 500;
[Reactive] [Reactive]
@ -31,19 +37,28 @@ public class MsgViewModel : MyReactiveObject
AppEvents.SendMsgViewRequested AppEvents.SendMsgViewRequested
.AsObservable() .AsObservable()
//.ObserveOn(RxSchedulers.MainThreadScheduler) //.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(content => _ = AppendQueueMsg(content)); .Subscribe(content => _ = AppendQueueMsg(content));
} }
private async Task AppendQueueMsg(string msg) private Task AppendQueueMsg(string msg)
{ {
if (AutoRefresh == false) if (AutoRefresh == false)
{ {
return; return Task.CompletedTask;
} }
EnqueueQueueMsg(msg); if (!EnqueueQueueMsg(msg))
{
return Task.CompletedTask;
}
TryScheduleDrain();
return Task.CompletedTask;
}
private void TryScheduleDrain()
{
if (!AppManager.Instance.ShowInTaskbar) if (!AppManager.Instance.ShowInTaskbar)
{ {
return; return;
@ -54,25 +69,59 @@ public class MsgViewModel : MyReactiveObject
return; return;
} }
_ = DrainQueueMsgAsync();
}
private async Task DrainQueueMsgAsync()
{
try try
{ {
await Task.Delay(500).ConfigureAwait(false); await Task.Delay(500).ConfigureAwait(false);
var sb = new StringBuilder(); var batch = DequeueBatch();
while (_queueMsg.TryDequeue(out var line)) if (_updateView != null && !string.IsNullOrEmpty(batch))
{ {
sb.Append(line); await _updateView(EViewAction.DispatcherShowMsg, batch);
} }
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
} }
finally finally
{ {
Interlocked.Exchange(ref _showLock, 0); Interlocked.Exchange(ref _showLock, 0);
} }
if (AppManager.Instance.ShowInTaskbar && !_queueMsg.IsEmpty)
{
TryScheduleDrain();
}
} }
private void EnqueueQueueMsg(string msg) private string DequeueBatch()
{
var sb = new StringBuilder();
var droppedCount = Interlocked.Exchange(ref _droppedMessageCount, 0);
if (droppedCount > 0)
{
sb.Append($"----- Message queue trimmed: dropped {droppedCount} messages -----{Environment.NewLine}");
}
var dequeuedCount = 0;
while (dequeuedCount < MaxFlushMessages
&& _queueMsg.TryDequeue(out var line))
{
Interlocked.Decrement(ref _queuedMessageCount);
sb.Append(line);
dequeuedCount++;
if (sb.Length >= MaxFlushChars)
{
break;
}
}
return sb.ToString();
}
private bool EnqueueQueueMsg(string msg)
{ {
//filter msg //filter msg
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
@ -81,23 +130,41 @@ public class MsgViewModel : MyReactiveObject
{ {
if (!Regex.IsMatch(msg, MsgFilter)) if (!Regex.IsMatch(msg, MsgFilter))
{ {
return; return false;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_queueMsg.Enqueue(ex.Message); msg = ex.Message;
_lastMsgFilterNotAvailable = true; _lastMsgFilterNotAvailable = true;
} }
} }
_queueMsg.Enqueue(msg); _queueMsg.Enqueue(NormalizeMessage(msg));
if (!msg.EndsWith(Environment.NewLine)) Interlocked.Increment(ref _queuedMessageCount);
TrimQueuedMessages();
return true;
}
private void TrimQueuedMessages()
{
var maxQueuedMessages = AppManager.Instance.ShowInTaskbar
? MaxQueuedMessagesVisible
: MaxQueuedMessagesHidden;
while (Volatile.Read(ref _queuedMessageCount) > maxQueuedMessages
&& _queueMsg.TryDequeue(out _))
{ {
_queueMsg.Enqueue(Environment.NewLine); Interlocked.Decrement(ref _queuedMessageCount);
Interlocked.Increment(ref _droppedMessageCount);
} }
} }
private static string NormalizeMessage(string msg)
{
return msg.EndsWith(Environment.NewLine) ? msg : msg + Environment.NewLine;
}
//public void ClearMsg() //public void ClearMsg()
//{ //{
// _queueMsg.Clear(); // _queueMsg.Clear();

View file

@ -43,24 +43,32 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
//var lineCount = txtMsg.LineCount;
//if (lineCount > ViewModel?.NumMaxMsg)
//{
// var cutLine = txtMsg.Document.GetLineByNumber(lineCount - KeepLines);
// txtMsg.Document.Remove(0, cutLine.Offset);
//}
if (txtMsg.LineCount > ViewModel?.NumMaxMsg)
{
ClearMsg();
}
txtMsg.AppendText(msg.ToString()); txtMsg.AppendText(msg.ToString());
TrimMsg();
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
} }
private void TrimMsg()
{
var maxLines = ViewModel?.NumMaxMsg ?? 500;
if (txtMsg.LineCount <= maxLines || txtMsg.Document == null)
{
return;
}
var firstKeepLine = txtMsg.LineCount - maxLines + 1;
var cutLine = txtMsg.Document.GetLineByNumber(firstKeepLine);
if (cutLine.Offset <= 0)
{
return;
}
txtMsg.Document.Remove(0, cutLine.Offset);
}
public void ClearMsg() public void ClearMsg()
{ {
txtMsg.Clear(); txtMsg.Clear();

View file

@ -45,18 +45,32 @@ public partial class MsgView
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
if (txtMsg.LineCount > ViewModel?.NumMaxMsg)
{
ClearMsg();
}
txtMsg.AppendText(msg.ToString()); txtMsg.AppendText(msg.ToString());
TrimMsg();
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
} }
private void TrimMsg()
{
var maxLines = ViewModel?.NumMaxMsg ?? 500;
if (txtMsg.LineCount <= maxLines)
{
return;
}
var keepFromCharIndex = txtMsg.GetCharacterIndexFromLineIndex(txtMsg.LineCount - maxLines);
if (keepFromCharIndex <= 0)
{
return;
}
txtMsg.Select(0, keepFromCharIndex);
txtMsg.SelectedText = string.Empty;
}
public void ClearMsg() public void ClearMsg()
{ {
txtMsg.Clear(); txtMsg.Clear();