能否使用ManualResetEvent等待固件最后一次属性变更通知?
关于在属性变更回调中等待最终状态的解决方案
嗨,咱们来一步步解决你的问题:你完全可以用ManualResetEvent实现这个等待逻辑,但得注意UI线程阻塞的坑——毕竟你的StartCurrentRun是按钮点击触发的UI线程方法,直接调用Wait()会把界面卡死,所以得结合后台线程来处理。当然,还有更贴合.NET异步模型的方案,我都给你详细说说:
一、使用ManualResetEvent的实现方式
1. 先声明事件实例
在你的类里加一个类级别的ManualResetEvent,初始状态设为未触发(false):
private ManualResetEvent _waitForProcessedEvent = new ManualResetEvent(false);
2. 修改StartCurrentRun方法
把等待逻辑放到后台线程里,避免阻塞UI:
private void StartCurrentRun(bool obj) { // 保留你原有的初始化代码 OriginalTime = SelectedVolumeEstimatedTime(); StartTimer(); WhenCancelledBlurVolumesGrid = false; IsLiveProgress = true; CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been started", SelectedAssay, SelectedVolume), LogInformationType.Info); var instance = ConnectToInstrument.InstrumentConnectionInstance; instance.InitalizeRun(PopulateRespectiveVolumes()); PropertyCallBackChangedInstance.PropertyChanged += PropertyCallBackChangedInstance_PropertyChanged; // 启动后台线程等待事件,别在UI线程等! Task.Run(() => { // 等待直到Processed状态触发事件 _waitForProcessedEvent.WaitOne(); // 这里写你要在最终状态后执行的逻辑 // 注意:操作UI必须切回UI线程 Application.Current.Dispatcher.Invoke(() => { // 比如更新UI状态、执行后续操作 }); // 重置事件,方便下次使用 _waitForProcessedEvent.Reset(); }); }
3. 在回调中触发事件
当检测到Processed状态时,触发ManualResetEvent:
private void PropertyCallBackChangedInstance_PropertyChanged(object sender, PropertyChangedEventArgs e) { try { // 保留你原有的其他逻辑 else if (e.PropertyName == "CurrentCartridgeStatusChanged") { var value = sender as InstrumentCallBackProperties; if (value.CurrentCartridgeStatusChanged == CurrentCartridgeStatus.Processed) { // 先触发事件,通知等待的线程 _waitForProcessedEvent.Set(); // 保留你原有的后续处理逻辑 PropertyCallBackChangedInstance.PropertyChanged -= PropertyCallBackChangedInstance_PropertyChanged; EstimatedTimeRemaining = "00:00:00"; stopWatch.Stop(); timer.Stop(); IsLiveProgress = false; CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been completed", SelectedAssay, SelectedVolume), LogInformationType.Info); if (IsRunSuccessfullyComplete != null && !WhenCancelledBlurVolumesGrid) { IsRunSuccessfullyComplete(); } WhenCancelledBlurVolumesGrid = true; if (ClearSelections != null) { ClearSelections(); } } } } catch (Exception ex) { CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} failed", SelectedAssay, SelectedVolume), LogInformationType.Error); // 异常也要触发事件,别让线程一直等下去 _waitForProcessedEvent.Set(); } }
⚠️ 注意:
- 绝对不能在UI线程调用
WaitOne(),否则界面会直接卡死; - 异常场景一定要触发事件,避免等待线程无限挂起;
- 使用后记得重置事件,确保下次运行能正常工作。
二、更优的异步方案:TaskCompletionSource
其实在现代.NET开发中,TaskCompletionSource是更推荐的方案——它更贴合异步编程模型,代码更简洁,还不用手动处理线程切换:
1. 声明TaskCompletionSource实例
在类里加一个类级别的变量:
private TaskCompletionSource<bool> _runCompletionTcs;
2. 修改StartCurrentRun为异步方法
把方法改成async void(因为是按钮命令,async void是允许的):
private async void StartCurrentRun(bool obj) { // 保留原有初始化代码 OriginalTime = SelectedVolumeEstimatedTime(); StartTimer(); WhenCancelledBlurVolumesGrid = false; IsLiveProgress = true; CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been started", SelectedAssay, SelectedVolume), LogInformationType.Info); var instance = ConnectToInstrument.InstrumentConnectionInstance; instance.InitalizeRun(PopulateRespectiveVolumes()); PropertyCallBackChangedInstance.PropertyChanged += PropertyCallBackChangedInstance_PropertyChanged; // 初始化TaskCompletionSource _runCompletionTcs = new TaskCompletionSource<bool>(); try { // 等待直到Processed状态触发,await会自动切回UI线程 await _runCompletionTcs.Task; // 这里写最终状态后的逻辑,直接操作UI就行,不用Dispatcher // 如果你的回调里已经处理了后续逻辑,这里可以不用重复 } catch (Exception ex) { CreateEventLogs.WriteToEventLog($"等待运行完成时出错: {ex.Message}", LogInformationType.Error); } finally { _runCompletionTcs = null; } }
3. 在回调中完成Task
当检测到Processed状态时,标记任务完成:
private void PropertyCallBackChangedInstance_PropertyChanged(object sender, PropertyChangedEventArgs e) { try { // 保留原有逻辑 else if (e.PropertyName == "CurrentCartridgeStatusChanged") { var value = sender as InstrumentCallBackProperties; if (value.CurrentCartridgeStatusChanged == CurrentCartridgeStatus.Processed) { // 通知await的代码继续执行 _runCompletionTcs?.TrySetResult(true); // 保留你原有的后续处理逻辑 PropertyCallBackChangedInstance.PropertyChanged -= PropertyCallBackChangedInstance_PropertyChanged; EstimatedTimeRemaining = "00:00:00"; stopWatch.Stop(); timer.Stop(); IsLiveProgress = false; CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been completed", SelectedAssay, SelectedVolume), LogInformationType.Info); if (IsRunSuccessfullyComplete != null && !WhenCancelledBlurVolumesGrid) { IsRunSuccessfullyComplete(); } WhenCancelledBlurVolumesGrid = true; if (ClearSelections != null) { ClearSelections(); } } } } catch (Exception ex) { CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} failed", SelectedAssay, SelectedVolume), LogInformationType.Error); // 异常时标记任务失败 _runCompletionTcs?.TrySetException(ex); } }
这个方案的优势:
- 用
await替代阻塞等待,完全不会卡UI; - 代码更符合.NET异步编程的最佳实践,可读性更强;
- 可以轻松结合
CancellationToken实现取消逻辑(如果需要的话)。
三、额外提醒
- 不管用哪种方案,一定要在取消运行、页面关闭或者异常时,正确触发信号/完成Task,避免线程挂起;
- 如果你的
RelayCommand不支持异步委托,可以换成支持异步的版本(比如MvvmLight的RelayCommand可以接受异步委托); - 记得在合适的时机取消注册
PropertyChanged事件,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Nikhil ANS
相关产品推荐
相关产品推荐

