You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:31:59