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

12 KiB
Raw Blame History

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

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 优点

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