mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-15 14:06:37 +08:00
更改了日志输出,更改了ChannelFlowTrigger存在的内存泄漏(取消超时机制)
This commit is contained in:
@@ -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(); // 隐藏窗体而不是关闭
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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; // 重置计数器
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user