NavisworksTransport/doc/working/UI架构重构技术设计方案_20250816.md

14 KiB
Raw Blame History

Navisworks WPF插件UI架构重构技术设计方案

文档信息

  • 创建日期: 2025-08-16
  • 版本: v1.0
  • 状态: 技术设计阶段
  • 作者: Claude技术架构师 + Claude Code

1. 项目背景

1.1 问题描述

当前Navisworks 2026 WPF插件在路径规划完成后频繁崩溃根本原因是将2017年的同步事件驱动架构强行迁移到异步WPF/MVVM模式导致严重的线程安全问题。

1.2 技术现状

  • 架构: WPF + MVVM模式
  • 数据绑定: ObservableCollection + PropertyChanged
  • 线程模型: 后台线程 + Dispatcher异步调用
  • 状态管理: 事件驱动 + 多层嵌套调用

1.3 关键问题

  1. 线程竞争: 后台线程直接修改UI绑定的ObservableCollection
  2. 事件重入: PropertyChanged事件链导致循环调用
  3. 调用链过深: 多层Dispatcher.BeginInvoke嵌套
  4. 状态不一致: UI更新缺乏原子性保证

2. 技术架构设计

2.1 总体架构图

┌─────────────────────────────────────────────────────────────┐
│                    UI Layer (WPF)                           │
├─────────────────────────────────────────────────────────────┤
│  LogisticsControlViewModel (重构)                          │
│  ├── ThreadSafeObservableCollection<PathRouteViewModel>    │
│  ├── UIStateManager.ExecuteUIUpdate()                      │
│  └── Command Pattern (替代事件驱动)                         │
├─────────────────────────────────────────────────────────────┤
│                 UI State Management                         │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │  UIStateManager │  │ ViewModelBase   │                  │
│  │  (线程安全)      │  │ (防重入机制)     │                  │
│  └─────────────────┘  └─────────────────┘                  │
├─────────────────────────────────────────────────────────────┤
│                Business Logic Layer                         │
│  PathPlanningManager (解耦UI)                              │
│  ├── IPathPlanningService (接口)                           │
│  ├── PathPlanningResult (数据传输对象)                      │
│  └── StatusUpdateCommand (命令模式)                         │
└─────────────────────────────────────────────────────────────┘

2.2 核心组件设计

2.2.1 UIStateManager - 统一UI状态管理器

public class UIStateManager
{
    private readonly SynchronizationContext _uiContext;
    private readonly object _lock = new object();
    private readonly Queue<UIUpdateOperation> _updateQueue;
    private volatile bool _isProcessing = false;

    // 线程安全的UI更新接口
    public Task ExecuteUIUpdateAsync<T>(Func<T> operation) where T : class
    {
        return Task.Run(() =>
        {
            lock (_lock)
            {
                if (_uiContext.CheckAccess())
                {
                    return operation();
                }
                else
                {
                    T result = default(T);
                    _uiContext.Send(_ => result = operation(), null);
                    return result;
                }
            }
        });
    }

    // 批量UI更新原子性保证
    public void ExecuteBatchUIUpdate(params Action[] updates)
    {
        if (_uiContext.CheckAccess())
        {
            foreach (var update in updates)
                update();
        }
        else
        {
            _uiContext.Send(_ =>
            {
                foreach (var update in updates)
                    update();
            }, null);
        }
    }
}

2.2.2 ThreadSafeObservableCollection - 线程安全集合

public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
    private readonly object _lock = new object();
    private readonly SynchronizationContext _synchronizationContext;

    public ThreadSafeObservableCollection()
    {
        _synchronizationContext = SynchronizationContext.Current;
        BindingOperations.EnableCollectionSynchronization(this, _lock);
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
        {
            _synchronizationContext.Send(_ => base.OnCollectionChanged(e), null);
        }
        else
        {
            base.OnCollectionChanged(e);
        }
    }

    // 批量操作接口
    public void AddRange(IEnumerable<T> items)
    {
        lock (_lock)
        {
            foreach (var item in items)
                Items.Add(item);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

2.2.3 重构的ViewModelBase - 防重入PropertyChanged

public class ViewModelBase : INotifyPropertyChanged
{
    private readonly UIStateManager _uiStateManager;
    private readonly HashSet<string> _updatingProperties = new HashSet<string>();
    private readonly object _propertyLock = new object();

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        lock (_propertyLock)
        {
            // 防重入检查
            if (_updatingProperties.Contains(propertyName))
                return;

            _updatingProperties.Add(propertyName);
        }

        try
        {
            _uiStateManager.ExecuteUIUpdateAsync(() =>
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            });
        }
        finally
        {
            lock (_propertyLock)
            {
                _updatingProperties.Remove(propertyName);
            }
        }
    }

    // 批量属性更新
    protected void SetProperties(params (string name, object value)[] properties)
    {
        _uiStateManager.ExecuteBatchUIUpdate(
            properties.Select(p => new Action(() => OnPropertyChanged(p.name))).ToArray()
        );
    }
}

2.2.4 Command Pattern替代事件驱动

public interface IPathPlanningCommand
{
    Task<PathPlanningResult> ExecuteAsync();
}

public class AutoPathPlanningCommand : IPathPlanningCommand
{
    private readonly Point3D _startPoint;
    private readonly Point3D _endPoint;
    private readonly PathPlanningManager _manager;

    public async Task<PathPlanningResult> ExecuteAsync()
    {
        // 纯业务逻辑不涉及UI
        var route = await _manager.AutoPlanPathAsync(_startPoint, _endPoint);
        return new PathPlanningResult
        {
            IsSuccess = route != null,
            Route = route,
            Message = route != null ? "规划成功" : "未找到可行路径"
        };
    }
}

// UI层处理
public class LogisticsControlViewModel : ViewModelBase
{
    private async void ExecuteAutoPlanPath()
    {
        var command = new AutoPathPlanningCommand(_startPoint, _endPoint, _pathPlanningManager);
        var result = await command.ExecuteAsync();

        // 使用UIStateManager统一处理UI更新
        await _uiStateManager.ExecuteUIUpdateAsync(() =>
        {
            if (result.IsSuccess)
            {
                // 原子性UI更新
                var viewModel = CreatePathRouteViewModel(result.Route);
                PathRoutes.Add(viewModel);
                SelectedPathRoute = viewModel;
                StatusText = result.Message;
            }
            else
            {
                StatusText = result.Message;
            }
        });
    }
}

2.3 状态机设计

public enum UIState
{
    Idle,
    Planning,
    Updating,
    Error
}

public class UIStateMachine
{
    private UIState _currentState = UIState.Idle;
    private readonly object _stateLock = new object();

    public bool TryTransition(UIState fromState, UIState toState)
    {
        lock (_stateLock)
        {
            if (_currentState == fromState)
            {
                _currentState = toState;
                return true;
            }
            return false;
        }
    }

    public UIState CurrentState
    {
        get { lock (_stateLock) return _currentState; }
    }
}

3. 实施计划

3.1 第一阶段基础设施搭建1-2周

3.1.1 创建核心组件

  • 实现UIStateManager类
  • 实现ThreadSafeObservableCollection类
  • 重构ViewModelBase基类
  • 创建UIStateMachine状态机

3.1.2 单元测试

  • UIStateManager线程安全测试
  • ThreadSafeObservableCollection并发测试
  • ViewModelBase防重入测试

3.2 第二阶段核心组件重构2-3周

3.2.1 重构LogisticsControlViewModel

public class LogisticsControlViewModel : ViewModelBase
{
    // 使用线程安全集合
    public ThreadSafeObservableCollection<PathRouteViewModel> PathRoutes { get; }
    
    // 使用UIStateManager处理所有UI更新
    private readonly UIStateManager _uiStateManager;
    
    // 使用状态机管理UI状态
    private readonly UIStateMachine _stateMachine;
    
    // 使用Command Pattern替代事件订阅
    public ICommand AutoPlanPathCommand { get; }
}

3.2.2 解耦PathPlanningManager

public interface IPathPlanningService
{
    Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin);
    Task<bool> ValidatePathAsync(PathRoute route);
}

public class PathPlanningManager : IPathPlanningService
{
    // 移除所有UI相关事件
    // 只保留纯业务逻辑
    
    public async Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin)
    {
        // 纯后台线程执行不触发任何UI事件
        return await Task.Run(() => {
            // 原有的路径规划逻辑
        });
    }
}

3.2.3 重新设计UI更新流程

旧流程 (有问题):
PathPlanningManager.AutoPlanPath() 
→ 后台线程触发StatusChanged事件 
→ UI线程订阅事件处理 
→ 直接修改ObservableCollection 
→ 嵌套Dispatcher调用 
→ 崩溃

新流程 (安全):
UI.ExecuteAutoPlanPath() 
→ Command.ExecuteAsync() 
→ PathPlanningService.AutoPlanPathAsync() 
→ 返回PathPlanningResult 
→ UIStateManager.ExecuteUIUpdateAsync() 
→ 原子性UI更新 
→ 完成

3.3 第三阶段测试验证1周

3.3.1 压力测试场景

  1. 重复规划测试: 连续执行100次"选择起点→选择终点→自动规划"
  2. 属性修改测试: 重复执行"通道→门→规划→删除→规划"场景
  3. 并发操作测试: 同时进行多个UI操作
  4. 内存泄漏测试: 长时间运行检查内存使用

3.3.2 功能验证清单

  • 自动路径规划功能正常
  • UI响应性能良好
  • 无UI线程阻塞
  • 无内存泄漏
  • 错误处理正确

3.4 第四阶段性能优化1周

3.4.1 UI性能优化

  • 减少不必要的PropertyChanged触发
  • 优化数据绑定性能
  • 实现虚拟化长列表

3.4.2 内存管理优化

  • 确保事件订阅正确释放
  • 优化大对象内存分配
  • 实现对象池模式

4. 风险控制

4.1 技术风险

  1. 兼容性风险: 新架构可能影响现有功能

    • 缓解措施: 分阶段迁移保持API接口不变
  2. 性能风险: 线程安全机制可能影响性能

    • 缓解措施: 性能基准测试,优化关键路径
  3. 复杂性风险: 新架构增加代码复杂度

    • 缓解措施: 详细文档,代码审查

4.2 进度风险

  1. 开发进度: 重构工作量可能超出预期

    • 缓解措施: 分阶段实施,每阶段可独立交付
  2. 测试时间: 充分测试需要时间

    • 缓解措施: 自动化测试,并行开发和测试

4.3 回滚策略

  • 每个阶段都在独立分支开发
  • 保持主分支稳定
  • 每个阶段完成后合并,确保可回滚

5. 预期效果

5.1 稳定性提升

  • 消除UI崩溃: 通过线程安全机制彻底解决崩溃问题
  • 提高可靠性: 原子性UI更新确保状态一致性
  • 增强健壮性: 防重入机制避免循环调用

5.2 维护性改善

  • 代码结构清晰: 分层架构,职责明确
  • 可测试性强: 依赖注入,便于单元测试
  • 可扩展性好: 接口设计,便于功能扩展

5.3 用户体验优化

  • 响应速度快: 异步操作UI不阻塞
  • 操作流畅: 无卡顿,无异常
  • 功能稳定: 重复操作不崩溃

6. 总结

本方案通过全面重构UI架构从根本上解决了WPF版本的稳定性问题。核心思想是

  1. 分离关注点: 业务逻辑与UI逻辑完全分离
  2. 线程安全: 统一的线程安全管理机制
  3. 原子操作: 确保UI更新的原子性
  4. 防重入: 避免事件循环调用
  5. 状态管理: 清晰的UI状态流转

通过这个重构方案将恢复2017版本的稳定性同时保持WPF现代UI的优势为后续功能扩展打下坚实基础。


本文档将根据实施进度持续更新,确保设计方案与实际开发保持同步。