为什么从计时器引发PropertyChanged事件会导致COMException



我正在使用XAML开发一个通用Windows平台应用程序,该应用程序在Windows 10 IoT Core下的Raspberry Pi上运行。该应用程序驱动I2C总线上的温度传感器。传感器类别为MLX90614Thermometer。传感器使用DispatcherTimer每隔100毫秒(大约)读取一次读数,并更新移动平均值。当移动平均值的变化超过指定阈值时,传感器会引发ValueChanged事件,并在事件args中提供新值。

在我的ViewModel类TemperatureSensorViewModel中,我订阅传感器的ValueChanged事件,并使用它更新名为AmbientChannel1Channel2的绑定属性。这些属性绑定到XAML UI中的文本块。这是事件处理程序:

    void HandleSensorValueChanged(object sender, SensorValueChangedEventArgs e)
    {
        switch (e.Channel)
        {
            case 0:
                Ambient = e.Value;
                break;
            case 1:
                Channel1 = e.Value;
                break;
            case 2:
                Channel2 = e.Value;
                break;
        }
    }

这里是CCD_ 10的示例数据绑定。。。

    <TextBlock x:Name="Ambient"  Grid.Row="1" Text="{Binding Path=Ambient}" Style="{StaticResource FieldValueStyle}" />

我使用的是MVVM Light Toolkit,所以我的属性是这样实现的(只显示了Ambient,但除了名称之外,其他都是相同的):

    public double Ambient
    {
        get { return ambientTemperature; }
        private set { Set(nameof(Ambient), ref ambientTemperature, value); }
    }

MVVM Light Toolkit提供Set()方法,该方法会自动引发正在设置的属性的PropertyChanged通知。

如果我响应按钮按下从传感器读取单个样本,这将正常工作。不过,只要我启用自动采样模式(基于定时器),它就会开始抛出COMExceptions。因此,这一定是与计时器有关的某种线程问题。

现在,如果我理解正确的话,运行时应该自动将PropertyChanged通知封送到UI线程上;从堆栈跟踪来看,情况似乎确实如此。然而,我最终得到了COMException。啊。

System.Runtime.InteropServices.COMException(0x8001010E):应用程序调用了一个为其他线程整理的接口。(HRESULT出现异常:0x8001010E(RPC_E_WRONG_THREAD))位于System.Runtime.InteropServices.WindowsRuntime.PropertyChangedEventArgsMarshaler.ConvertToNative(PropertyChangedEventArgs managedArgs)位于System.ComponentModel.PropertyChangedEventHandler.Invoke(对象发送方,PropertyChangedEventArgs e)在GalaSoft.MvvvmLight.ObsObservableObject.RisePropertyChanged(字符串属性名称)在GalaSoft.MvvvmLight.ViewModelBase.RisePropertyChanged[T](字符串属性名称,T旧值,T新值,布尔广播)在GalaSoft.MvvvmLight.ViewModelBase.Set[T](字符串属性名称,T&field,T newValue,布尔广播)在TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.set_Channel1(双值)在TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.HandSensorValueChanged(Object sender,SensorValueChangedEventArgs e)在TA.UWP.Devices.MLX90614Thermometer.RiseValueChanged(UInt32通道,双值)在TA.UWP.Devices.MLX90614温度计.SampleAllChannels()位于TA.UWP.Devices.MLX90614温度计.b_37_0()位于System.Threading.Tasks.Task.InerInvoke()位于System.Threading.Tasks.Task.Execute()---从引发异常的前一位置开始的堆栈结尾跟踪---在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)位于System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)位于System.Runtime.CompilerServices.TaskAwaiter.GetResult()在TA.UWP.Devices.MLX90614温度计.d_37.MoveNext()---从引发异常的前一位置开始的堆栈结尾跟踪---在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)位于System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)位于System.Runtime.CompilerServices.TaskAwaiter.GetResult()在TA.UWP.Devices.MLX90614温度计.d_38.MoveNext()

瓦特?我不明白这里发生了什么。有人能看到可能是什么问题吗?

经过进一步的研究,我想我可以回答我自己的问题。。。

关于PropertyChanged事件被自动编组到UI线程,我似乎做了一个无效的假设。我在一些关于WPF的文章中读到了这一点,但正如@Clemens在评论中指出的那样,这不是我们谈论的WPF,而是通用Windows平台,它是Windows运行时(WinRT)的衍生物。

  • 关键学习:UWP中的XAML≠WPF 在考虑通用Windows应用程序时,不能依赖WPF文档

然后我发现这个问题和我的有相似之处,特别是海报犯了我的错误,认为他在处理WPF。接受的答案让我想到了另一个关于MVVM Light Toolkit的DispatcherHelper类的问题,该类可用于将任何代码封送到调度器线程上。

所以,我似乎必须自己进行线程编组(我真的很讨厌Windows编程的这一方面,我希望微软能开发出线程安全的UI技术!)。

因此,我更新了我的属性以使用以下模式:

    public double Ambient
    {
        get { return ambientTemperature; }
        private set
        {
            ambientTemperature = value;
            DispatcherHelper.CheckBeginInvokeOnUI(() => RaisePropertyChanged());
        }
    }

现在,这似乎如预期的那样起作用。

我认为很多人都会陷入这种困境,所以我把这个答案留在这里,希望人们能在需要的时候找到它。

我不认为运行时对PropertyChanged事件进行任何编组,这确实有点令人恼火。

你可能想做一些类似的事情:

public double Ambient
{
    get { return ambientTemperature; }
    private set
    { 
        Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                Set(nameof(Ambient), ref ambientTemperature, value);
            });            
    }
}

以便在UI线程上实现这一点。

相关内容

  • 没有找到相关文章

最新更新