清理废弃的连线标记,更新异步方法调用方式
This commit is contained in:
parent
fcc87b2cb0
commit
760786d9b1
@ -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]
|
||||
|
||||
335
doc/working/Command框架同步异步双模式设计方案.md
Normal file
335
doc/working/Command框架同步异步双模式设计方案.md
Normal file
@ -0,0 +1,335 @@
|
||||
# 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 受影响的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<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:同步/异步双模式 | 消除警告、意图清晰 | 需要修改基类 | ⭐⭐⭐⭐⭐ |
|
||||
| 方案2:Task.FromResult | 简单 | 仍是伪异步 | ⭐⭐⭐ |
|
||||
| 方案3:明确注释 | 不改代码 | 仍有警告 | ⭐⭐ |
|
||||
| 方案4:pragma warning | 消除警告 | 隐藏问题 | ⭐ |
|
||||
|
||||
## 6. 总结
|
||||
|
||||
**推荐方案**:方案1(同步/异步双模式)
|
||||
|
||||
**理由**:
|
||||
1. 从根本上解决问题,而不是隐藏问题
|
||||
2. 提高代码可读性和可维护性
|
||||
3. 为未来的Command开发提供更好的模式
|
||||
4. 向后兼容,风险可控
|
||||
|
||||
**注意事项**:
|
||||
1. 实施前需要充分测试
|
||||
2. 需要更新相关文档
|
||||
3. 需要团队培训新的使用模式
|
||||
@ -300,12 +300,6 @@ namespace NavisworksTransport
|
||||
/// </summary>
|
||||
public List<CircleMarker> PointMarkers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连线标记集合(已废弃,保留用于兼容)
|
||||
/// </summary>
|
||||
[Obsolete("使用ControlLineMarkers或PathLineMarkers代替")]
|
||||
public List<LineMarker> LineMarkers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 控制点连线集合(用户意图,半透明)
|
||||
/// </summary>
|
||||
@ -342,7 +336,6 @@ namespace NavisworksTransport
|
||||
public PathVisualization()
|
||||
{
|
||||
PointMarkers = new List<CircleMarker>();
|
||||
LineMarkers = new List<LineMarker>();
|
||||
ControlLineMarkers = new List<LineMarker>();
|
||||
PathLineMarkers = new List<LineMarker>();
|
||||
TangentMarkers = new List<SquareMarker>();
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user