TURBO-568:修复PLC连接和相机连接时序问题导致的内存访问异常

This commit is contained in:
李伟
2026-04-21 16:00:46 +08:00
parent 12882bd1c6
commit 188bac53f1
2 changed files with 94 additions and 48 deletions
+53 -26
View File
@@ -150,6 +150,22 @@ namespace XplorePlane
Log.Error(ex, "射线源资源释放失败"); Log.Error(ex, "射线源资源释放失败");
} }
// 先停止导航相机实时采集,再释放资源,避免回调死锁
try
{
var bootstrapper = AppBootstrapper.Instance;
if (bootstrapper != null)
{
var cameraVm = bootstrapper.Container.Resolve<NavigationPropertyPanelViewModel>();
cameraVm?.Dispose();
Log.Information("导航相机 ViewModel 已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "导航相机 ViewModel 释放失败");
}
// 释放导航相机服务资源 // 释放导航相机服务资源
try try
{ {
@@ -232,6 +248,8 @@ namespace XplorePlane
private bool _modulesInitialized = false; private bool _modulesInitialized = false;
private string? _cameraError;
protected override Window CreateShell() protected override Window CreateShell()
{ {
// 提前初始化模块,确保硬件服务在 MainWindow XAML 解析前已注册 // 提前初始化模块,确保硬件服务在 MainWindow XAML 解析前已注册
@@ -241,46 +259,55 @@ namespace XplorePlane
_modulesInitialized = true; _modulesInitialized = true;
} }
// 在主窗口显示前完成导航相机连接,避免窗口弹出后再检索导致卡顿 var shell = Container.Resolve<MainWindow>();
TryConnectCamera();
return Container.Resolve<MainWindow>(); // 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化
shell.Loaded += (s, e) =>
{
TryConnectCamera();
// 通知 ViewModel 相机状态已确定,启动实时预览或显示错误
try
{
var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
cameraVm.OnCameraReady();
}
catch (Exception ex)
{
Log.Error(ex, "通知相机 ViewModel 失败");
}
if (_cameraError != null)
{
HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error);
}
};
return shell;
} }
/// <summary> /// <summary>
/// 在独立 MTA 线程检索并连接导航相机,带 10 秒超时 /// 在线程检索并连接导航相机。
/// 连接成功后 NavigationPropertyPanelViewModel 构造时拿到的就是已连接的导航相机 /// pylon SDK 要求在主线程(STA)上操作,不能放到后台线程
/// </summary> /// </summary>
private void TryConnectCamera() private void TryConnectCamera()
{ {
var camera = Container.Resolve<ICameraController>(); var camera = Container.Resolve<ICameraController>();
CameraInfo? info = null;
Exception? error = null;
var thread = new System.Threading.Thread(() => try
{ {
try { info = camera.Open(); } var info = camera.Open();
catch (Exception ex) { error = ex; } Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber);
});
thread.IsBackground = true;
thread.SetApartmentState(System.Threading.ApartmentState.MTA);
thread.Start();
bool completed = thread.Join(TimeSpan.FromSeconds(10));
if (!completed)
{
Log.Warning("导航相机检索超时 (10s),跳过自动连接");
HexMessageBox.Show("导航相机检索超时(10秒),请检查导航相机连接后重启软件。", MessageBoxButton.OK, MessageBoxImage.Error);
} }
else if (error != null) catch (DeviceNotFoundException)
{ {
Log.Warning(error, "导航相机自动连接失败: {Message}", error.Message); Log.Warning("未检测到导航相机");
HexMessageBox.Show($"导航相机连接失败: {error.Message}", MessageBoxButton.OK, MessageBoxImage.Error); _cameraError = "未检测到导航相机,请检查连接后重启软件。";
} }
else catch (Exception ex)
{ {
Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info!.ModelName, info.SerialNumber); Log.Warning(ex, "导航相机自动连接失败: {Message}", ex.Message);
_cameraError = $"导航相机连接失败: {ex.Message}";
} }
} }
@@ -177,23 +177,29 @@ namespace XplorePlane.ViewModels
RefreshCameraParamsCommand = new DelegateCommand(RefreshCameraParams, () => IsCameraConnected); RefreshCameraParamsCommand = new DelegateCommand(RefreshCameraParams, () => IsCameraConnected);
OpenCameraSettingsCommand = new DelegateCommand(OpenCameraSettings, () => IsCameraConnected); OpenCameraSettingsCommand = new DelegateCommand(OpenCameraSettings, () => IsCameraConnected);
// 相机已在 App 启动阶段连接,直接检测状态并启动实时预览 CameraStatusText = "正在检索相机...";
if (_camera.IsConnected) }
{
_camera.ImageGrabbed += OnCameraImageGrabbed;
_camera.GrabError += OnCameraGrabError;
_camera.ConnectionLost += OnCameraConnectionLost;
IsCameraConnected = true; /// <summary>
CameraStatusText = "已连接"; /// 相机连接完成后由外部调用,启动实时预览。
RefreshCameraParams(); /// </summary>
StartGrab(); public void OnCameraReady()
IsLiveViewEnabled = true; {
} if (!_camera.IsConnected)
else
{ {
CameraStatusText = "未检测到相机"; CameraStatusText = "未检测到相机";
return;
} }
_camera.ImageGrabbed += OnCameraImageGrabbed;
_camera.GrabError += OnCameraGrabError;
_camera.ConnectionLost += OnCameraConnectionLost;
IsCameraConnected = true;
CameraStatusText = "已连接";
RefreshCameraParams();
StartGrab();
IsLiveViewEnabled = true;
} }
#region Camera Methods #region Camera Methods
@@ -344,15 +350,18 @@ namespace XplorePlane.ViewModels
private void OnCameraImageGrabbed(object? sender, ImageGrabbedEventArgs e) private void OnCameraImageGrabbed(object? sender, ImageGrabbedEventArgs e)
{ {
if (_disposed) return;
try try
{ {
var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat); var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat);
var app = Application.Current; var app = Application.Current;
if (app == null) return; if (app == null) return;
app.Dispatcher.Invoke(() => app.Dispatcher.BeginInvoke(() =>
{ {
CameraImageSource = bitmap; if (!_disposed)
CameraImageSource = bitmap;
}); });
if (_liveViewRunning) if (_liveViewRunning)
@@ -362,7 +371,8 @@ namespace XplorePlane.ViewModels
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error(ex, "Failed to process camera image"); if (!_disposed)
_logger.Error(ex, "Failed to process camera image");
} }
} }
@@ -372,9 +382,10 @@ namespace XplorePlane.ViewModels
var app = Application.Current; var app = Application.Current;
if (app == null) return; if (app == null) return;
app.Dispatcher.Invoke(() => app.Dispatcher.BeginInvoke(() =>
{ {
CameraStatusText = $"采集错误: {e.ErrorDescription}"; if (!_disposed)
CameraStatusText = $"采集错误: {e.ErrorDescription}";
}); });
} }
@@ -384,8 +395,9 @@ namespace XplorePlane.ViewModels
var app = Application.Current; var app = Application.Current;
if (app == null) return; if (app == null) return;
app.Dispatcher.Invoke(() => app.Dispatcher.BeginInvoke(() =>
{ {
if (_disposed) return;
IsCameraConnected = false; IsCameraConnected = false;
IsCameraGrabbing = false; IsCameraGrabbing = false;
CameraStatusText = "连接已断开"; CameraStatusText = "连接已断开";
@@ -400,12 +412,19 @@ namespace XplorePlane.ViewModels
public void Dispose() public void Dispose()
{ {
if (_disposed) return; if (_disposed) return;
_disposed = true;
_liveViewRunning = false; _liveViewRunning = false;
try { _camera.Dispose(); } // 先取消事件订阅,防止回调继续触发
catch (Exception ex) { _logger.Error(ex, "Error disposing camera"); } _camera.ImageGrabbed -= OnCameraImageGrabbed;
_camera.GrabError -= OnCameraGrabError;
_camera.ConnectionLost -= OnCameraConnectionLost;
_disposed = true; // 停止采集后再关闭连接
try { if (_camera.IsGrabbing) _camera.StopGrabbing(); } catch { }
try { _camera.Close(); } catch { }
_logger.Information("NavigationPropertyPanelViewModel disposed");
} }
#endregion IDisposable #endregion IDisposable