12 KiB
12 KiB
Command框架同步/异步双模式设计方案
文档信息
- 创建日期:2026-01-14
- 状态:待实施
- 优先级:中
- 相关文件:
src/Commands/CommandBase.cssrc/Commands/IPathPlanningCommand.cs
1. 当前问题
1.1 问题描述
Command框架要求所有Command的 ExecuteInternalAsync 方法必须返回 Task<PathPlanningResult>,导致:
- 伪异步Command:很多Command标记为
async但内部没有await操作 - CS1998编译警告:编译器提示"此异步方法缺少await运算符"
- 代码意图不清晰:无法区分哪些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:
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
// 使用await进行真正的异步操作
await _uiStateManager.ExecuteUIUpdateAsync(() => { });
var pathPlanTask = _pathPlanningManager.AutoPlanPath(...);
generatedRoute = await pathPlanTask;
}
SetLogisticsAttributeCommand:
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
// 使用Task.Run在后台线程执行
await Task.Run(() => {
// 批量设置物流属性
});
}
2. 设计方案
2.1 核心思路
同步/异步双模式:允许Command选择性地实现同步或异步版本,框架自动选择合适的执行方式。
2.2 接口设计
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改造)
改造前:
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("完成");
}
}
改造后:
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(保持不变)
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 优点
- ✅ 消除CS1998警告:同步Command不需要
async关键字 - ✅ 代码意图清晰:通过方法签名明确表达同步/异步意图
- ✅ 向后兼容:异步Command不需要修改
- ✅ 灵活性:Command可以选择最适合自己的执行模式
- ✅ 类型安全:编译时检查,避免运行时错误
3.2 缺点
- ❌ 需要修改基类:需要修改
CommandBase.cs - ❌ 增加复杂度:需要维护同步和异步两套方法
- ❌ 反射开销:
SupportsAsyncExecution使用反射判断(可以优化为虚方法)
3.3 风险评估
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| 破坏现有Command | 高 | 低 | 向后兼容设计 |
| 性能开销 | 低 | 中 | 优化反射判断逻辑 |
| 代码复杂度增加 | 中 | 高 | 添加详细注释和文档 |
4. 实施计划
4.1 阶段一:基类修改
-
修改
CommandBase.cs:- 添加
ExecuteInternal虚方法 - 修改
ExecuteInternalAsync默认实现 - 添加
SupportsAsyncExecution属性 - 修改
ExecuteAsync方法
- 添加
-
更新
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 阶段三:测试验证
- 单元测试:测试同步和异步Command都能正常工作
- 集成测试:测试Command框架的执行流程
- 回归测试:确保所有现有功能正常
4.4 阶段四:文档更新
- 更新Command框架文档
- 添加Command开发指南
- 更新代码注释
5. 替代方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 方案1:同步/异步双模式 | 消除警告、意图清晰 | 需要修改基类 | ⭐⭐⭐⭐⭐ |
| 方案2:Task.FromResult | 简单 | 仍是伪异步 | ⭐⭐⭐ |
| 方案3:明确注释 | 不改代码 | 仍有警告 | ⭐⭐ |
| 方案4:pragma warning | 消除警告 | 隐藏问题 | ⭐ |
6. 总结
推荐方案:方案1(同步/异步双模式)
理由:
- 从根本上解决问题,而不是隐藏问题
- 提高代码可读性和可维护性
- 为未来的Command开发提供更好的模式
- 向后兼容,风险可控
注意事项:
- 实施前需要充分测试
- 需要更新相关文档
- 需要团队培训新的使用模式