更改了日志输出,更改了ChannelFlowTrigger存在的内存泄漏(取消超时机制)

This commit is contained in:
fengjiayi
2024-09-21 10:06:44 +08:00
parent a1ecd259dd
commit 3537a49784
15 changed files with 624 additions and 304 deletions

View File

@@ -5,21 +5,102 @@ namespace Serein.WorkBench
/// <summary>
/// DebugWindow.xaml 的交互逻辑
/// </summary>
using System;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
public partial class LogWindow : Window
{
private StringBuilder logBuffer = new StringBuilder();
private int logUpdateInterval = 100; // 批量更新的时间间隔(毫秒)
private Timer logUpdateTimer;
private const int MaxLines = 1000; // 最大显示的行数
private bool autoScroll = true; // 自动滚动标识
public LogWindow()
{
InitializeComponent();
// 初始化定时器,用于批量更新日志
logUpdateTimer = new Timer(logUpdateInterval);
logUpdateTimer.Elapsed += (s, e) => FlushLog(); // 定时刷新日志
logUpdateTimer.Start();
// 添加滚动事件处理,判断用户是否手动滚动
// LogTextBox.ScrollChanged += LogTextBox_ScrollChanged;
}
/// <summary>
/// 添加日志到缓冲区
/// </summary>
public void AppendText(string text)
{
Dispatcher.BeginInvoke(() =>
lock (logBuffer)
{
LogTextBox.AppendText(text);
LogTextBox.ScrollToEnd();
logBuffer.Append(text); // 将日志添加到缓冲区中
}
}
/// <summary>
/// 清空日志缓冲区并更新到 TextBox 中
/// </summary>
private void FlushLog()
{
if (logBuffer.Length == 0) return;
Dispatcher.Invoke(() =>
{
lock (logBuffer)
{
LogTextBox.AppendText(logBuffer.ToString());
logBuffer.Clear(); // 清空缓冲区
}
TrimLog(); // 检查并修剪日志长度
ScrollToEndIfNeeded(); // 根据条件滚动到末尾
});
}
/// <summary>
/// 限制日志输出的最大行数,超出时删除旧日志
/// </summary>
private void TrimLog()
{
if (LogTextBox.LineCount > MaxLines)
{
// 删除最早的多余行
LogTextBox.Text = LogTextBox.Text.Substring(LogTextBox.GetCharacterIndexFromLineIndex(LogTextBox.LineCount - MaxLines));
}
}
/// <summary>
/// 检测用户是否手动滚动了文本框
/// </summary>
private void LogTextBox_ScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
{
if (e.ExtentHeightChange == 0) // 用户手动滚动时
{
// 判断是否滚动到底部
// autoScroll = LogTextBox.VerticalOffset == LogTextBox.ScrollableHeight;
}
}
/// <summary>
/// 根据 autoScroll 标志决定是否滚动到末尾
/// </summary>
private void ScrollToEndIfNeeded()
{
if (autoScroll)
{
LogTextBox.ScrollToEnd(); // 仅在需要时滚动到末尾
}
}
/// <summary>
/// 清空日志
/// </summary>
public void Clear()
{
Dispatcher.BeginInvoke(() =>
@@ -27,15 +108,28 @@ namespace Serein.WorkBench
LogTextBox.Clear();
});
}
/// <summary>
/// 点击清空日志按钮时触发
/// </summary>
private void ClearLog_Click(object sender, RoutedEventArgs e)
{
LogTextBox.Clear();
}
/// <summary>
/// 窗口关闭事件,隐藏窗体而不是关闭
/// </summary>
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//logUpdateTimer?.Stop();
//logUpdateTimer?.Close();
//logUpdateTimer?.Dispose();
logBuffer?.Clear();
Clear();
e.Cancel = true; // 取消关闭操作
this.Hide(); // 隐藏窗体而不是关闭
}
}
}

View File

@@ -87,6 +87,7 @@ namespace Serein.WorkBench
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging;
private bool IsSelectDragging;
/// <summary>
/// 当前选取的控件
@@ -823,8 +824,9 @@ namespace Serein.WorkBench
}
}
if (IsSelectControl /*&& e.LeftButton == MouseButtonState.Pressed*/) // 正在选取节点
if (IsSelectControl) // 正在选取节点
{
IsSelectDragging = e.LeftButton == MouseButtonState.Pressed;
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(FlowChartCanvas);
@@ -1407,6 +1409,36 @@ namespace Serein.WorkBench
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!IsSelectControl)
{
// 进入选取状态
IsSelectControl = true;
IsSelectDragging = false; // 初始化为非拖动状态
// 记录鼠标起始点
startSelectControolPoint = e.GetPosition(FlowChartCanvas);
// 初始化选取矩形的位置和大小
Canvas.SetLeft(SelectionRectangle, startSelectControolPoint.X);
Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y);
SelectionRectangle.Width = 0;
SelectionRectangle.Height = 0;
// 显示选取矩形
SelectionRectangle.Visibility = Visibility.Visible;
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
// 捕获鼠标以便在鼠标移动到Canvas外部时仍能处理事件
FlowChartCanvas.CaptureMouse();
}
else
{
// 如果已经是选取状态,单击则认为结束框选
CompleteSelection();
}
e.Handled = true; // 防止事件传播影响其他控件
return;
// 如果正在选取状态,再次点击画布时自动确定选取范围,否则进入选取状态
if (IsSelectControl)
{
@@ -1467,14 +1499,71 @@ namespace Serein.WorkBench
}
/// <summary>
/// 在画布中释放鼠标按下,结束选取状态
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsSelectControl)
{
// 松开鼠标时判断是否为拖动操作
if (IsSelectDragging)
{
// 完成拖动框选
CompleteSelection();
}
// 释放鼠标捕获
FlowChartCanvas.ReleaseMouseCapture();
}
e.Handled = true;
}
/// 完成选取操作
/// </summary>
private void CompleteSelection()
{
IsSelectControl = false;
// 隐藏选取矩形
SelectionRectangle.Visibility = Visibility.Collapsed;
// 获取选取范围
Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle),
Canvas.GetTop(SelectionRectangle),
SelectionRectangle.Width,
SelectionRectangle.Height);
// 处理选取范围内的控件
// selectNodeControls.Clear();
foreach (UIElement element in FlowChartCanvas.Children)
{
Rect elementBounds = new Rect(Canvas.GetLeft(element), Canvas.GetTop(element),
element.RenderSize.Width, element.RenderSize.Height);
if (selectionArea.Contains(elementBounds))
{
if (element is NodeControlBase control)
{
selectNodeControls.Add(control);
}
}
}
// 选中后的操作
SelectedNode();
}
private ContextMenu ConfiguerSelectionRectangle()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) =>
{
if(selectNodeControls.Count > 0)
if (selectNodeControls.Count > 0)
{
foreach(var node in selectNodeControls.ToArray())
foreach (var node in selectNodeControls.ToArray())
{
var guid = node?.ViewModel?.Node?.Guid;
if (!string.IsNullOrEmpty(guid))
@@ -1488,25 +1577,11 @@ namespace Serein.WorkBench
return contextMenu;
// nodeControl.ContextMenu = contextMenu;
}
/// <summary>
/// 在画布中释放鼠标按下,结束选取状态
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsSelectControl)
{
}
}
private void SelectedNode()
{
if(selectNodeControls.Count == 0)
{
Console.WriteLine($"没有选择控件");
//Console.WriteLine($"没有选择控件");
SelectionRectangle.Visibility = Visibility.Collapsed;
return;
}
@@ -1514,7 +1589,7 @@ namespace Serein.WorkBench
foreach (var node in selectNodeControls)
{
node.ViewModel.Selected();
node.ViewModel.CancelSelect();
// node.ViewModel.CancelSelect();
node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
node.BorderThickness = new Thickness(4);
}
@@ -1849,8 +1924,8 @@ namespace Serein.WorkBench
await FlowEnvironment.StartAsync(); // 快
//await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/5
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5
//await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5,定时器精度丢失
}
/// <summary>
@@ -1975,6 +2050,11 @@ namespace Serein.WorkBench
/// <param name="e"></param>
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
e.Handled = true; // 禁止默认的Tab键行为
}
if (e.KeyStates == Keyboard.GetKeyStates(Key.Escape))
//if (Keyboard.Modifiers == ModifierKeys.Shift)
{
@@ -1984,6 +2064,7 @@ namespace Serein.WorkBench
EndConnection();
SelectionRectangle.Visibility = Visibility.Collapsed;
CancelSelectNode();
}
}

View File

@@ -94,7 +94,7 @@ namespace Serein.WorkBench.Node.ViewModel
// }
//}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading.Channels;
namespace Serein.WorkBench.tool
{
@@ -9,21 +10,20 @@ namespace Serein.WorkBench.tool
/// </summary>
public class LogTextWriter : TextWriter
{
private readonly Action<string> logAction;
private readonly StringWriter stringWriter = new();
private readonly BlockingCollection<string> logQueue = new();
private readonly Task logTask;
// 用于计数的字段
private int writeCount = 0;
private const int maxWrites = 500;
private readonly Action clearTextBoxAction;
private readonly Action<string> logAction; // 更新日志UI的委托
private readonly StringWriter stringWriter = new(); // 缓存日志内容
private readonly Channel<string> logChannel = Channel.CreateUnbounded<string>(); // 日志管道
private readonly Action clearTextBoxAction; // 清空日志UI的委托
private int writeCount = 0; // 写入计数器
private const int maxWrites = 500; // 写入最大计数
public LogTextWriter(Action<string> logAction, Action clearTextBoxAction)
{
this.logAction = logAction;
this.clearTextBoxAction = clearTextBoxAction;
logTask = Task.Run(ProcessLogQueue); // 异步处理日志
// 异步启动日志处理任务,不阻塞主线程
Task.Run(ProcessLogQueueAsync);
}
public override Encoding Encoding => Encoding.UTF8;
@@ -54,39 +54,32 @@ namespace Serein.WorkBench.tool
EnqueueLog();
}
// 将日志加入通道
private void EnqueueLog()
{
logQueue.Add(stringWriter.ToString());
var log = stringWriter.ToString();
stringWriter.GetStringBuilder().Clear();
}
private async Task ProcessLogQueue()
{
foreach (var log in logQueue.GetConsumingEnumerable())
if (!logChannel.Writer.TryWrite(log))
{
// 异步执行日志输出操作
await Task.Run(() =>
{
logAction(log);
// 计数器增加
writeCount++;
if (writeCount >= maxWrites)
{
// 计数器达到50清空文本框
clearTextBoxAction?.Invoke();
writeCount = 0; // 重置计数器
}
});
// 如果写入失败(不太可能),则直接丢弃日志或处理
}
}
public new void Dispose()
// 异步处理日志队列
private async Task ProcessLogQueueAsync()
{
logQueue.CompleteAdding();
logTask.Wait();
base.Dispose();
await foreach (var log in logChannel.Reader.ReadAllAsync()) // 异步读取日志通道
{
logAction?.Invoke(log); // 执行日志写入到UI的委托
writeCount++;
if (writeCount >= maxWrites)
{
clearTextBoxAction?.Invoke(); // 清空文本框
writeCount = 0; // 重置计数器
}
}
}
}
}