NavisworksTransport/doc/working/Command框架同步异步双模式设计方案.md

335 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Command框架同步/异步双模式设计方案
## 文档信息
- **创建日期**2026-01-14
- **状态**:待实施
- **优先级**:中
- **相关文件**
- `src/Commands/CommandBase.cs`
- `src/Commands/IPathPlanningCommand.cs`
## 1. 当前问题
### 1.1 问题描述
Command框架要求所有Command的 `ExecuteInternalAsync` 方法必须返回 `Task<PathPlanningResult>`,导致:
1. **伪异步Command**很多Command标记为 `async` 但内部没有 `await` 操作
2. **CS1998编译警告**:编译器提示"此异步方法缺少await运算符"
3. **代码意图不清晰**无法区分哪些Command真正需要异步哪些只是同步操作
### 1.2 受影响的Command8个
| 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<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
// 使用await进行真正的异步操作
await _uiStateManager.ExecuteUIUpdateAsync(() => { });
var pathPlanTask = _pathPlanningManager.AutoPlanPath(...);
generatedRoute = await pathPlanTask;
}
```
**SetLogisticsAttributeCommand**
```csharp
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
// 使用Task.Run在后台线程执行
await Task.Run(() => {
// 批量设置物流属性
});
}
```
## 2. 设计方案
### 2.1 核心思路
**同步/异步双模式**允许Command选择性地实现同步或异步版本框架自动选择合适的执行方式。
### 2.2 接口设计
```csharp
public abstract class CommandBase : IPathPlanningCommand
{
/// <summary>
/// 同步执行命令(默认实现)
/// 子类可以重写此方法以提供同步实现
/// </summary>
protected virtual PathPlanningResult ExecuteInternal(CancellationToken cancellationToken)
{
throw new NotImplementedException(
$"Command {DisplayName} 必须实现 ExecuteInternal 或 ExecuteInternalAsync");
}
/// <summary>
/// 异步执行命令(可选实现)
/// 子类可以重写此方法以提供异步实现
/// 如果子类重写了此方法,框架将优先使用异步版本
/// </summary>
protected virtual Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
// 默认实现:将同步方法包装为异步
return Task.FromResult(ExecuteInternal(cancellationToken));
}
/// <summary>
/// 判断Command是否支持异步执行
/// </summary>
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;
}
}
/// <summary>
/// 异步执行命令(框架入口)
/// </summary>
public async Task<PathPlanningResult> 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<PathPlanningResult> 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<PathPlanningResult> 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同步/异步双模式 | 消除警告、意图清晰 | 需要修改基类 | ⭐⭐⭐⭐⭐ |
| 方案2Task.FromResult | 简单 | 仍是伪异步 | ⭐⭐⭐ |
| 方案3明确注释 | 不改代码 | 仍有警告 | ⭐⭐ |
| 方案4pragma warning | 消除警告 | 隐藏问题 | ⭐ |
## 6. 总结
**推荐方案**方案1同步/异步双模式)
**理由**
1. 从根本上解决问题,而不是隐藏问题
2. 提高代码可读性和可维护性
3. 为未来的Command开发提供更好的模式
4. 向后兼容,风险可控
**注意事项**
1. 实施前需要充分测试
2. 需要更新相关文档
3. 需要团队培训新的使用模式