修改UI更新的定时器线程安全导致崩溃问题

This commit is contained in:
tian 2025-09-05 12:59:11 +08:00
parent 1f82eb814f
commit 722e2ce9cc
9 changed files with 258 additions and 2833 deletions

View File

@ -1491,7 +1491,7 @@ public async Task SafeCacheRefreshAsync()
- `DatabaseDockPane/Models.cs` - 数据库操作示例
- `ClashDetective` 相关示例 - 高级功能示例
碰撞检测模式说明
### 碰撞检测模式说明
Hard硬碰撞
@ -1559,3 +1559,89 @@ public async Task SafeCacheRefreshAsync()
collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
```
### ModelGeometry 属性
ModelGeometry类公开了以下成员
属性
| 名称 | 说明 |
|----------------------------|----------------------------------------|
| ActiveColor | 此几何体的当前(可见)颜色 |
| ActiveTransform | 返回几何体当前活动的变换矩阵 |
| ActiveTransparency | 此几何体的当前(可见)透明度 |
| BoundingBox | 此几何体在世界坐标系中的包围盒 |
| FragmentCount | 此几何体被分割成的片段数量 |
| IsDisposed | 获取一个值指示对象是否已被释放且不再可用继承自NativeHandle |
| IsReadOnly | 是否只读重写NativeHandle的IsReadOnly属性 |
| IsSolid | 此几何体是否为实体?(包括所有形成封闭、流形外壳的三角形图元) |
| Item | 模型层次结构中对应此几何体的项目 |
| OriginalColor | 此几何体的原始颜色(设计文件中指定的) |
| OriginalTransform | 返回几何体加载时的原始变换矩阵 |
| OriginalTransparency | 此几何体的原始透明度(设计文件中指定的) |
| PermanentColor | 几何体的永久颜色。可能是原始颜色或用户明确覆盖的颜色 |
| PermanentOverrideTransform | 应用于模型几何体原始变换的覆盖变换 |
| PermanentTransform | 模型几何体的永久变换。由原始变换与覆盖变换组合形成的变换 |
| PermanentTransparency | 几何体的永久透明度。可能是原始透明度或用户明确覆盖的透明度 |
| PrimitiveCount | 定义此几何体的图元(三角形、线、点)数量 |
| PrimitiveTypes | 用于定义此几何体的图元类型 |
这些属性提供了访问和查询Navisworks模型几何体各种状态信息的接口包括颜色、变换、透明度、包围盒和几何体结构等重要属性。
```csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using Autodesk.Navisworks.Api.Controls;
static public void OutputFirstGeometry()
{
try
{
if (Autodesk.Navisworks.Api.Application.ActiveDocument != null &&
!Autodesk.Navisworks.Api.Application.ActiveDocument.IsClear)
{
ModelGeometry first =
Autodesk.Navisworks.Api.Application.ActiveDocument.
Models[0].RootItem.FindFirstGeometry();
if (first != null)
{
string text = string.Empty;
text = string.Format("ActiveColor = {0}" +
"\nActiveTransparency = {1}" +
"\nBoundingBox = {2}" +
"\nFragmentCount {3}" +
"\nIsSolid {4}" +
"\nItem {5}" +
"\nOriginalColor {6}" +
"\nOriginalTransparency {7}" +
"\nPermanentColor {8}" +
"\nPermanentTransparency {9}",
first.ActiveColor.ToString(),
first.ActiveTransparency,
first.BoundingBox.ToString(),
first.FragmentCount,
first.IsSolid.ToString(),
first.Item.ToString(),
first.OriginalColor.ToString(),
first.OriginalTransparency,
first.PermanentColor.ToString(),
first.PermanentTransparency);
MessageBox.Show(text);
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message + "\n\n" + e.ToString());
}
}
```

View File

@ -6,7 +6,6 @@ using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi;
using NavisworksTransport.UI.WPF;
using NavisworksTransport.Utils;
namespace NavisworksTransport

File diff suppressed because it is too large Load Diff

View File

@ -48,19 +48,8 @@ namespace NavisworksTransport
// 捕获任务调度器未观察到的异常
System.Threading.Tasks.TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
// 尝试设置线程异常处理,但如果失败就跳过(可能控件已创建)
try
{
System.Windows.Forms.Application.ThreadException += OnThreadException;
System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
LogManager.Info("[全局异常] WinForms异常处理已设置");
}
catch (InvalidOperationException ex)
{
LogManager.Warning($"[全局异常] 无法设置WinForms异常处理模式控件已创建: {ex.Message}");
// 仍然可以添加事件处理器
System.Windows.Forms.Application.ThreadException += OnThreadException;
}
// 直接注册WinForms线程异常处理器不调用SetUnhandledExceptionMode避免插件环境下的警告
System.Windows.Forms.Application.ThreadException += OnThreadException;
_isInitialized = true;
@ -319,6 +308,15 @@ namespace NavisworksTransport
[DockPanePlugin(420, 700, FixedSize = false, AutoScroll = true)]
public class Main : DockPanePlugin
{
/// <summary>
/// 构造函数,提前初始化全局异常处理器
/// </summary>
public Main()
{
// 在任何控件创建之前初始化全局异常处理器
GlobalExceptionHandler.Initialize();
}
public override Control CreateControlPane()
{
try
@ -374,4 +372,5 @@ namespace NavisworksTransport
}, "销毁控制面板");
}
}
}

View File

@ -743,11 +743,15 @@ namespace NavisworksTransport.Core
{
try
{
if (_flushTimer != null)
var timer = _flushTimer;
if (timer != null)
{
_flushTimer = null; // 先置空,防止回调继续执行
LogManager.Info("停止保底定时器");
_flushTimer.Dispose();
_flushTimer = null;
// 使用Change方法停止定时器然后安全释放
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Dispose();
}
}
catch (Exception ex)

View File

@ -1133,9 +1133,9 @@ namespace NavisworksTransport.PathPlanning
LogManager.Info($"[包围盒障碍物处理] 输入统计 - 总模型项: {totalItems}, 将排除通道元素: {channelItemsSet.Count}");
// 使用并行处理提高性能,使用75%的CPU内核以平衡性能和稳定性
// 使用并行处理提高性能,使用50%的CPU内核以平衡性能和稳定性
var lockObject = new object();
Parallel.ForEach(allItems, new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount * 3 / 4) }, item =>
Parallel.ForEach(allItems, new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) }, item =>
{
try
{

View File

@ -96,8 +96,7 @@ namespace NavisworksTransport.UI.WPF
{
LogManager.Info("=== 物流路径规划插件初始化开始 ===");
// 初始化全局异常处理
GlobalExceptionHandler.Initialize();
// 全局异常处理器已在Main构造函数中初始化此处无需重复初始化
// 初始化路径规划管理器
InitializePathPlanningManager();

View File

@ -572,10 +572,29 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
// 先在UI线程上创建快照确保线程安全
var pointsSnapshot = await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
return Points?.ToList() ?? new List<PathPointViewModel>();
});
// 然后在后台线程计算
return await Task.Run(() =>
{
double totalLength = 0.0;
var pointsSnapshot = Points.ToList();
if (pointsSnapshot.Count < 2)
{
// 在UI线程上更新总长度
_uiStateManager.QueueUIUpdate(() =>
{
_totalLength = 0.0;
OnPropertyChanged(nameof(TotalLength));
OnPropertyChanged(nameof(SummaryInfo));
});
return 0.0;
}
for (int i = 0; i < pointsSnapshot.Count - 1; i++)
{
@ -617,27 +636,36 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
// 先在UI线程上创建快照确保线程安全
var pointsSnapshot = await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
return Points?.ToList() ?? new List<PathPointViewModel>();
});
var nameSnapshot = await _uiStateManager.ExecuteUIUpdateAsync(() => _name);
// 然后在后台线程验证
return await Task.Run(() =>
{
bool isValid = true;
var validationMessages = new List<string>();
// 检查路径点数量
if (Points.Count < 2)
if (pointsSnapshot.Count < 2)
{
isValid = false;
validationMessages.Add("路径至少需要2个点");
}
// 检查路径名称
if (string.IsNullOrWhiteSpace(_name))
if (string.IsNullOrWhiteSpace(nameSnapshot))
{
isValid = false;
validationMessages.Add("路径名称不能为空");
}
// 检查重复点
var duplicatePoints = Points.ToList()
var duplicatePoints = pointsSnapshot
.GroupBy(p => new { p.X, p.Y, p.Z })
.Where(g => g.Count() > 1)
.ToList();
@ -657,7 +685,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
OnPropertyChanged(nameof(ValidationStatus));
});
LogManager.Debug($"路径验证完成:{_name},结果:{(isValid ? "" : "")}");
LogManager.Debug($"路径验证完成:{nameSnapshot},结果:{(isValid ? "" : "")}");
return isValid;
});
}

View File

@ -456,12 +456,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
AnimationDuration = 10.0;
CurrentAnimationTime = 0.0;
// 设置初始状态
CanStartAnimation = true;
// 设置初始状态 - 修改: 默认状态应该是未激活,等待有效动画
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画";
AnimationStatus = "动画状态: 就绪";
AnimationProgress = 0;
// 初始化碰撞检测状态
@ -479,6 +477,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 初始化动画管理器设置
UpdatePathAnimationManagerSettings();
// 修改: 使用新的按钮状态更新方法来设置正确的初始状态
UpdateAnimationButtonStates();
LogManager.Info("动画设置初始化完成");
}
@ -719,24 +720,20 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
CurrentPathRoute = pathRoute;
// 更新按钮状态
var hasValidPath = pathRoute != null && pathRoute.Points.Count >= 2;
CanStartAnimation = hasValidPath;
// 使用新的统一按钮状态更新方法
UpdateAnimationButtonStates();
// 更新状态文本
// 更新碰撞状态
if (pathRoute == null)
{
AnimationStatus = "动画状态: 请选择路径";
CollisionStatus = "请选择路径";
}
else if (pathRoute.Points.Count < 2)
{
AnimationStatus = "动画状态: 路径点数不足需要至少2个点";
CollisionStatus = "路径点数不足";
}
else
{
AnimationStatus = "动画状态: 就绪";
CollisionStatus = "就绪";
}
@ -878,15 +875,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// <summary>
/// 执行清除移动物体命令
/// </summary>
/// <summary>
/// 执行清除移动物体命令
/// 清除移动物体选择、停止当前动画并更新按钮状态
/// </summary>
private void ExecuteClearAnimatedObject()
{
try
{
LogManager.Info("开始清除移动物体选择并恢复原始位置");
LogManager.Info("开始清除移动物体选择、当前动画并恢复原始位置");
// 首先停止当前动画(如果正在播放)
if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating)
{
try
{
_pathAnimationManager.StopAnimation();
LogManager.Info("已停止当前播放的动画");
}
catch (Exception stopEx)
{
LogManager.Warning($"停止当前动画时出现警告: {stopEx.Message}");
}
}
// 如果有选中的物体,则恢复到原始位置
if (SelectedAnimatedObject != null)
@ -918,7 +927,22 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 清理选择状态
SelectedAnimatedObject = null;
LogManager.Info("移动物体选择已完全清除");
// 重置动画进度和状态
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
// 清理碰撞检测结果
HasCollisionResults = false;
CollisionStatus = "就绪";
CollisionSummary = "尚未运行碰撞检测";
// 更新按钮状态 - 清除动画后应该重新评估按钮状态
UpdateAnimationButtonStates();
// 更新生成动画的能力状态
UpdateCanGenerateAnimation();
LogManager.Info("移动物体选择和当前动画已完全清除,按钮状态已更新");
}
catch (Exception ex)
{
@ -1047,9 +1071,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 使用PathAnimationManager设置物体动画真正的物体移动动画
_pathAnimationManager.SetupAnimation(SelectedAnimatedObject, pathPoints, AnimationDuration);
// 更新状态
CanStartAnimation = true;
AnimationStatus = "动画已生成,可以开始播放";
// 更新状态 - 使用新的按钮状态更新方法
UpdateAnimationButtonStates();
GenerationStatus = "动画生成成功";
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
@ -1129,6 +1152,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
GenerationStatus = "可以生成动画";
}
// 同时更新动画按钮状态,因为对象或路径的变化会影响"开始动画"按钮的可用性
UpdateAnimationButtonStates();
}
/// <summary>
@ -1146,6 +1172,78 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CollisionDetectionFrequency = 10.0; // 默认值
}
}
/// <summary>
/// 统一更新动画按钮状态逻辑
/// 根据当前的路径、动画对象和动画管理器状态来决定按钮的可用性
/// </summary>
private void UpdateAnimationButtonStates()
{
try
{
// 检查是否有有效的动画可以播放
var hasValidPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
var hasValidAnimatedObject = SelectedAnimatedObject != null;
var hasValidAnimation = hasValidPath && hasValidAnimatedObject;
// 检查动画管理器当前状态
var animationState = _pathAnimationManager?.CurrentState ?? NavisworksTransport.Core.Animation.AnimationState.Stopped;
var isAnimating = _pathAnimationManager?.IsAnimating ?? false;
// 根据动画状态和条件更新按钮状态
switch (animationState)
{
case NavisworksTransport.Core.Animation.AnimationState.Playing:
// 播放中:开始按钮禁用,暂停和停止按钮可用
CanStartAnimation = false;
CanPauseAnimation = true;
CanStopAnimation = true;
AnimationStatus = "动画状态: 播放中";
break;
case NavisworksTransport.Core.Animation.AnimationState.Paused:
// 暂停中:开始按钮可用(显示为继续),暂停按钮禁用,停止按钮可用
CanStartAnimation = hasValidAnimation;
CanPauseAnimation = false;
CanStopAnimation = true;
StartAnimationButtonText = "继续播放";
AnimationStatus = "动画状态: 已暂停";
break;
default:
// 停止或其他状态:根据是否有有效动画来决定开始按钮状态
CanStartAnimation = hasValidAnimation;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画";
// 更新状态文本
if (!hasValidPath && !hasValidAnimatedObject)
{
AnimationStatus = "动画状态: 请选择路径和移动物体";
}
else if (!hasValidPath)
{
AnimationStatus = "动画状态: 请选择有效路径至少2个点";
}
else if (!hasValidAnimatedObject)
{
AnimationStatus = "动画状态: 请选择移动物体";
}
else
{
AnimationStatus = "动画状态: 就绪";
}
break;
}
LogManager.Debug($"按钮状态已更新: CanStart={CanStartAnimation}, CanPause={CanPauseAnimation}, CanStop={CanStopAnimation}, Status={AnimationStatus}");
}
catch (Exception ex)
{
LogManager.Error($"更新动画按钮状态时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 初始化参数更新防抖定时器