清理废弃的连线标记,更新异步方法调用方式

This commit is contained in:
tian 2026-01-14 12:12:22 +08:00
parent fcc87b2cb0
commit 760786d9b1
5 changed files with 344 additions and 16 deletions

View File

@ -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]

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

View File

@ -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();

View File

@ -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)
{

View File

@ -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)