# 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. 需要团队培训新的使用模式