From 760786d9b10a7ccd1f432a98ed345b15bee412ee Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 14 Jan 2026 12:12:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E5=BA=9F=E5=BC=83=E7=9A=84?= =?UTF-8?q?=E8=BF=9E=E7=BA=BF=E6=A0=87=E8=AE=B0=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E6=96=B9=E6=B3=95=E8=B0=83=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- default_config.toml | 4 +- .../Command框架同步异步双模式设计方案.md | 335 ++++++++++++++++++ src/Core/PathPointRenderPlugin.cs | 11 +- .../ViewModels/AnimationControlViewModel.cs | 2 +- .../ViewModels/SystemManagementViewModel.cs | 8 +- 5 files changed, 344 insertions(+), 16 deletions(-) create mode 100644 doc/working/Command框架同步异步双模式设计方案.md diff --git a/default_config.toml b/default_config.toml index a9fab39..8d3d81e 100644 --- a/default_config.toml +++ b/default_config.toml @@ -20,7 +20,7 @@ vehicle_height_meters = 2.0 # 安全间隙(米) safety_margin_meters = 0.05 -# 路径默认转弯半径(米)- 表示路径允许的最大转弯半径 +# 路径默认转弯半径(米) default_path_turn_radius = 2.5 # 圆弧采样步长(米)- 推荐值:0.02-0.1 @@ -40,7 +40,7 @@ duration_seconds = 10.0 # 检测间隙(米) detection_gap_meters = 0.05 -# 空间索引格子大小(米)- 用于动画碰撞检测的空间索引,推荐值:车辆宽度 +# 空间索引格子大小(米) spatial_index_cell_size = 1.0 [logistics] diff --git a/doc/working/Command框架同步异步双模式设计方案.md b/doc/working/Command框架同步异步双模式设计方案.md new file mode 100644 index 0000000..16f7773 --- /dev/null +++ b/doc/working/Command框架同步异步双模式设计方案.md @@ -0,0 +1,335 @@ +# Command框架同步/异步双模式设计方案 + +## 文档信息 + +- **创建日期**:2026-01-14 +- **状态**:待实施 +- **优先级**:中 +- **相关文件**: + - `src/Commands/CommandBase.cs` + - `src/Commands/IPathPlanningCommand.cs` + +## 1. 当前问题 + +### 1.1 问题描述 + +Command框架要求所有Command的 `ExecuteInternalAsync` 方法必须返回 `Task`,导致: + +1. **伪异步Command**:很多Command标记为 `async` 但内部没有 `await` 操作 +2. **CS1998编译警告**:编译器提示"此异步方法缺少await运算符" +3. **代码意图不清晰**:无法区分哪些Command真正需要异步,哪些只是同步操作 + +### 1.2 受影响的Command(8个) + +| Command | 文件 | 行号 | 风险等级 | 原因 | +|---------|------|------|---------|------| +| FloorAnalysisCommand.ExecuteAsync | LayerManagementCommands.cs | 154 | 极高 | 直接调用Navisworks COM API | +| PreviewSplitCommand.ExecuteAsync | LayerManagementCommands.cs | 410 | 极高 | 直接调用Navisworks COM API | +| VoxelPathFindingTestCommand.ExecuteInternalAsync | VoxelPathFindingTestCommand.cs | 55 | 高 | 内部调用Navisworks COM API | +| GenerateCollisionReportCommand.ExecuteInternalAsync | GenerateCollisionReportCommand.cs | 132 | 中 | 可能包含COM API | +| ProcessSingleLayerAsync | LayerManagementViewModel.cs | 1549 | 低 | 已正确处理线程安全 | +| ProcessSingleLayerAsync | LayerManagementViewModel.cs | 2255 | 低 | 已正确处理线程安全 | +| ProcessSingleLayerAsync | ModelSplitterManager.cs | 1128 | 低 | 已正确处理线程安全 | +| VoxelGridSDFTestCommand.ExecuteInternalAsync | VoxelGridSDFTestCommand.cs | 53 | 高 | 内部调用Navisworks COM API | + +### 1.3 真正异步的Command示例 + +**AutoPathPlanningCommand**: +```csharp +protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken) +{ + // 使用await进行真正的异步操作 + await _uiStateManager.ExecuteUIUpdateAsync(() => { }); + var pathPlanTask = _pathPlanningManager.AutoPlanPath(...); + generatedRoute = await pathPlanTask; +} +``` + +**SetLogisticsAttributeCommand**: +```csharp +protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken) +{ + // 使用Task.Run在后台线程执行 + await Task.Run(() => { + // 批量设置物流属性 + }); +} +``` + +## 2. 设计方案 + +### 2.1 核心思路 + +**同步/异步双模式**:允许Command选择性地实现同步或异步版本,框架自动选择合适的执行方式。 + +### 2.2 接口设计 + +```csharp +public abstract class CommandBase : IPathPlanningCommand +{ + /// + /// 同步执行命令(默认实现) + /// 子类可以重写此方法以提供同步实现 + /// + protected virtual PathPlanningResult ExecuteInternal(CancellationToken cancellationToken) + { + throw new NotImplementedException( + $"Command {DisplayName} 必须实现 ExecuteInternal 或 ExecuteInternalAsync"); + } + + /// + /// 异步执行命令(可选实现) + /// 子类可以重写此方法以提供异步实现 + /// 如果子类重写了此方法,框架将优先使用异步版本 + /// + protected virtual Task ExecuteInternalAsync(CancellationToken cancellationToken) + { + // 默认实现:将同步方法包装为异步 + return Task.FromResult(ExecuteInternal(cancellationToken)); + } + + /// + /// 判断Command是否支持异步执行 + /// + protected virtual bool SupportsAsyncExecution + { + get + { + // 检查子类是否重写了ExecuteInternalAsync + var asyncMethod = GetType().GetMethod( + "ExecuteInternalAsync", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var baseMethod = typeof(CommandBase).GetMethod( + "ExecuteInternalAsync", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + return asyncMethod?.DeclaringType != baseMethod?.DeclaringType; + } + } + + /// + /// 异步执行命令(框架入口) + /// + public async Task ExecuteAsync(CancellationToken cancellationToken = default) + { + if (Status == CommandExecutionStatus.Executing) + { + return PathPlanningResult.Failure("命令正在执行中,请勿重复执行"); + } + + var stopwatch = Stopwatch.StartNew(); + + try + { + // 创建内部取消令牌源,组合外部令牌 + _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var combinedToken = _cancellationTokenSource.Token; + + // 验证阶段 + Status = CommandExecutionStatus.Validating; + UpdateProgress(0, "正在验证命令参数..."); + + var validationResult = await ValidateAsync(combinedToken); + if (!validationResult.IsSuccess) + { + Status = CommandExecutionStatus.Failed; + validationResult.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds; + return validationResult; + } + + // 执行阶段 + Status = CommandExecutionStatus.Executing; + UpdateProgress(10, "开始执行命令..."); + + PathPlanningResult result; + + // 根据Command是否支持异步,选择执行方式 + if (SupportsAsyncExecution) + { + LogManager.Info($"命令 {DisplayName} 使用异步执行模式"); + result = await ExecuteInternalAsync(combinedToken); + } + else + { + LogManager.Info($"命令 {DisplayName} 使用同步执行模式"); + result = ExecuteInternal(combinedToken); + } + + // 设置执行时间 + result.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds; + + // 根据结果设置状态 + if (combinedToken.IsCancellationRequested) + { + Status = CommandExecutionStatus.Cancelled; + return PathPlanningResult.Failure("命令执行已取消"); + } + else if (result.IsSuccess) + { + Status = CommandExecutionStatus.Completed; + UpdateProgress(100, "命令执行完成"); + } + else + { + Status = CommandExecutionStatus.Failed; + } + + return result; + } + catch (OperationCanceledException) + { + Status = CommandExecutionStatus.Cancelled; + LogManager.Error($"命令 {DisplayName} 执行被取消"); + return PathPlanningResult.Failure("命令执行已取消"); + } + catch (Exception ex) + { + Status = CommandExecutionStatus.Failed; + LogManager.Error($"命令 {DisplayName} 执行出现异常", ex); + return PathPlanningResult.Failure($"命令执行失败: {ex.Message}", ex); + } + finally + { + stopwatch.Stop(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + } +} +``` + +### 2.3 使用示例 + +#### 示例1:同步Command(伪异步Command改造) + +**改造前**: +```csharp +public class VoxelPathFindingTestCommand : CommandBase +{ + protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken) + { + // CS1998警告:此异步方法缺少await运算符 + var spaceBounds = GetSpaceBounds(_document); + var allModelItems = GetAllModelItems(_document); + var voxelGrid = generator.GenerateFromBIMWithSDF(...); + return PathPlanningResult.Success("完成"); + } +} +``` + +**改造后**: +```csharp +public class VoxelPathFindingTestCommand : CommandBase +{ + protected override PathPlanningResult ExecuteInternal(CancellationToken cancellationToken) + { + // 同步执行,不再有CS1998警告 + var spaceBounds = GetSpaceBounds(_document); + var allModelItems = GetAllModelItems(_document); + var voxelGrid = generator.GenerateFromBIMWithSDF(...); + return PathPlanningResult.Success("完成"); + } +} +``` + +#### 示例2:异步Command(保持不变) + +```csharp +public class AutoPathPlanningCommand : CommandBase +{ + protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken) + { + // 真正的异步操作 + await _uiStateManager.ExecuteUIUpdateAsync(() => { }); + var pathPlanTask = _pathPlanningManager.AutoPlanPath(...); + generatedRoute = await pathPlanTask; + return PathPlanningResult.Success("完成"); + } +} +``` + +## 3. 优缺点分析 + +### 3.1 优点 + +1. ✅ **消除CS1998警告**:同步Command不需要 `async` 关键字 +2. ✅ **代码意图清晰**:通过方法签名明确表达同步/异步意图 +3. ✅ **向后兼容**:异步Command不需要修改 +4. ✅ **灵活性**:Command可以选择最适合自己的执行模式 +5. ✅ **类型安全**:编译时检查,避免运行时错误 + +### 3.2 缺点 + +1. ❌ **需要修改基类**:需要修改 `CommandBase.cs` +2. ❌ **增加复杂度**:需要维护同步和异步两套方法 +3. ❌ **反射开销**:`SupportsAsyncExecution` 使用反射判断(可以优化为虚方法) + +### 3.3 风险评估 + +| 风险 | 影响 | 概率 | 缓解措施 | +|------|------|------|---------| +| 破坏现有Command | 高 | 低 | 向后兼容设计 | +| 性能开销 | 低 | 中 | 优化反射判断逻辑 | +| 代码复杂度增加 | 中 | 高 | 添加详细注释和文档 | + +## 4. 实施计划 + +### 4.1 阶段一:基类修改 + +1. 修改 `CommandBase.cs`: + - 添加 `ExecuteInternal` 虚方法 + - 修改 `ExecuteInternalAsync` 默认实现 + - 添加 `SupportsAsyncExecution` 属性 + - 修改 `ExecuteAsync` 方法 + +2. 更新 `IPathPlanningCommand.cs`(如果需要) + +### 4.2 阶段二:Command改造 + +按风险等级逐步改造: + +**第一批(低风险)**: +- LayerManagementViewModel.ProcessSingleLayerAsync (2处) +- ModelSplitterManager.ProcessSingleLayerAsync (1处) + +**第二批(高风险)**: +- LayerManagementCommands.ExecuteAsync (2处) +- VoxelPathFindingTestCommand.ExecuteInternalAsync (1处) +- VoxelGridSDFTestCommand.ExecuteInternalAsync (1处) +- GenerateCollisionReportCommand.ExecuteInternalAsync (1处) + +### 4.3 阶段三:测试验证 + +1. 单元测试:测试同步和异步Command都能正常工作 +2. 集成测试:测试Command框架的执行流程 +3. 回归测试:确保所有现有功能正常 + +### 4.4 阶段四:文档更新 + +1. 更新Command框架文档 +2. 添加Command开发指南 +3. 更新代码注释 + +## 5. 替代方案对比 + +| 方案 | 优点 | 缺点 | 推荐度 | +|------|------|------|--------| +| 方案1:同步/异步双模式 | 消除警告、意图清晰 | 需要修改基类 | ⭐⭐⭐⭐⭐ | +| 方案2:Task.FromResult | 简单 | 仍是伪异步 | ⭐⭐⭐ | +| 方案3:明确注释 | 不改代码 | 仍有警告 | ⭐⭐ | +| 方案4:pragma warning | 消除警告 | 隐藏问题 | ⭐ | + +## 6. 总结 + +**推荐方案**:方案1(同步/异步双模式) + +**理由**: +1. 从根本上解决问题,而不是隐藏问题 +2. 提高代码可读性和可维护性 +3. 为未来的Command开发提供更好的模式 +4. 向后兼容,风险可控 + +**注意事项**: +1. 实施前需要充分测试 +2. 需要更新相关文档 +3. 需要团队培训新的使用模式 \ No newline at end of file diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index 673d79f..d162ce1 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -300,12 +300,6 @@ namespace NavisworksTransport /// public List PointMarkers { get; set; } - /// - /// 连线标记集合(已废弃,保留用于兼容) - /// - [Obsolete("使用ControlLineMarkers或PathLineMarkers代替")] - public List LineMarkers { get; set; } - /// /// 控制点连线集合(用户意图,半透明) /// @@ -342,7 +336,6 @@ namespace NavisworksTransport public PathVisualization() { PointMarkers = new List(); - LineMarkers = new List(); ControlLineMarkers = new List(); PathLineMarkers = new List(); TangentMarkers = new List(); @@ -710,7 +703,8 @@ namespace NavisworksTransport { // 清空现有标记 visualization.PointMarkers.Clear(); - visualization.LineMarkers.Clear(); // 确保没有连线 + visualization.ControlLineMarkers.Clear(); // 确保没有控制点连线 + visualization.PathLineMarkers.Clear(); // 确保没有路径连线 var points = visualization.PathRoute.Points; if (points.Count == 0) return; @@ -1066,7 +1060,6 @@ namespace NavisworksTransport { // 清空现有标记 visualization.PointMarkers.Clear(); - visualization.LineMarkers.Clear(); visualization.ControlLineMarkers.Clear(); visualization.PathLineMarkers.Clear(); visualization.TangentMarkers.Clear(); diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 4d5dd58..c5ab126 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -118,7 +118,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 直接使用现有的报告生成命令,但指定测试名称 var command = GenerateCollisionReportCommand.CreateComprehensive(autoHighlight: false); command.SetTestName(testName); - command.ExecuteAsync(); + _ = command.ExecuteAsync(); } catch (Exception ex) { diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs index 4b767c7..5fb20c1 100644 --- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs +++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs @@ -142,8 +142,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels InitializeCommands(); // 初始化系统管理设置 - InitializeSystemManagementSettingsAsync(); - + _ = InitializeSystemManagementSettingsAsync(); + LogManager.Info("SystemManagementViewModel构造函数执行完成"); } catch (Exception ex) @@ -184,8 +184,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels InitializeCommands(); // 初始化系统管理设置 - InitializeSystemManagementSettingsAsync(); - + _ = InitializeSystemManagementSettingsAsync(); + LogManager.Info("SystemManagementViewModel构造函数执行完成 - 支持统一状态栏"); } catch (Exception ex)