我正在开发一个使用XAML的通用Windows平台应用程序,该应用程序在Windows 10 IoT Core
下的Raspberry Pi上运行。该应用程序驱动I2C总线上的温度传感器。传感器类为MLX90614Thermometer
。传感器使用DispatcherTimer
每100毫秒(大约)读取读数并更新移动平均值。当移动平均值变化超过指定阈值时,传感器会引发ValueChanged
事件并在事件参数中提供新值。
在我的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
...
<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): 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?我不明白这里发生了什么。任何人都可以看到问题所在吗?
答案 0 :(得分:5)
经过进一步研究,我想我可以回答我自己的问题......
似乎我对自动编组到UI线程的PropertyChanged事件做了一个无效的假设。我在一些关于WPF的文章中读过这篇文章,但正如@Clemens在评论中指出的那样,这不是我们所说的WPF,它是通用Windows平台,它是Windows运行时(WinRT)的衍生物。
然后我发现this question与我有相似之处,特别是海报错误地假设他正在处理WPF。接受的答案引导我进入另一个question regarding the MVVM Light Toolkit's DispatcherHelper
class,它可用于将任何代码编组到调度程序线程上。
所以,似乎我必须进行自己的线程编组(我真的很讨厌Windows编程的这个方面,我希望微软能够创建一个线程安全的UI技术!)。
所以我更新了我的属性以使用这种模式:
public double Ambient
{
get { return ambientTemperature; }
private set
{
ambientTemperature = value;
DispatcherHelper.CheckBeginInvokeOnUI(() => RaisePropertyChanged());
}
}
现在似乎按预期工作了。
我想很多人都会陷入这个泥潭,所以我在这里留下这个答案,希望人们在需要时能找到答案。
答案 1 :(得分:2)
我不认为运行时会对PropertyChanged
事件进行任何类型的编组操作,这无疑会让人感到恼火。
你可能想做类似的事情:
public double Ambient
{
get { return ambientTemperature; }
private set
{
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
Set(nameof(Ambient), ref ambientTemperature, value);
});
}
}
在UI线程上实现这一点。