我正在使用在Raspberry Pi下运行的XAML开发通用Windows平台应用程序Windows 10 IoT Core
.该应用程序驱动I2C总线上的温度传感器.传感器类是MLX90614Thermometer
.传感器使用a DispatcherTimer
每100毫秒(大约)获取读数并更新移动平均值.当移动平均值变化超过指定阈值时,传感器会引发ValueChanged
事件并在事件args中提供新值.
在我的ViewModel类中TemperatureSensorViewModel
,我订阅了传感器的ValueChanged
事件并使用它来更新命名的绑定属性Ambient
,Channel1
和Channel2
.这些属性绑定到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; } }
...这里是一个示例数据绑定Ambient
...
我正在使用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): The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)) at System.Runtime.InteropServices.WindowsRuntime.PropertyChangedEventArgsMarshaler.ConvertToNative(PropertyChangedEventArgs managedArgs) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e) at GalaSoft.MvvmLight.ObservableObject.RaisePropertyChanged(String propertyName) at GalaSoft.MvvmLight.ViewModelBase.RaisePropertyChanged[T](String propertyName, T oldValue, T newValue, Boolean broadcast) at GalaSoft.MvvmLight.ViewModelBase.Set[T](String propertyName, T& field, T newValue, Boolean broadcast) at TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.set_Channel1(Double value) at TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.HandleSensorValueChanged(Object sender, SensorValueChangedEventArgs e) at TA.UWP.Devices.MLX90614Thermometer.RaiseValueChanged(UInt32 channel, Double value) at TA.UWP.Devices.MLX90614Thermometer.SampleAllChannels() at TA.UWP.Devices.MLX90614Thermometer.b__37_0() at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at TA.UWP.Devices.MLX90614Thermometer.d__37.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at TA.UWP.Devices.MLX90614Thermometer.d__38.MoveNext()
WAT?我不明白这里发生了什么.任何人都可以看到问题可能是什么?
经过进一步的研究,我想我可以回答我自己的问题......
似乎我对自动编组到UI线程的PropertyChanged事件做了一个无效的假设.我在一些关于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()); } }
现在似乎按预期工作.
我想很多人都会陷入这个泥潭,所以我在这里留下这个答案,希望人们在需要时能找到它.