2

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

在我的 ViewModel 类中TemperatureSensorViewModel,我订阅了传感器的ValueChanged事件并使用它来更新名为AmbientChannel1的绑定属性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):应用程序调用了为不同线程编组的接口。(来自 HRESULT 的异常:0x8001010E (RPC_E_WRONG_THREAD))
   在 System.Runtime.InteropServices.WindowsRuntime.PropertyChangedEventArgsMarshaler.ConvertToNative(PropertyChangedEventArgs managedArgs)
   在 System.ComponentModel.PropertyChangedEventHandler.Invoke(对象发送者,PropertyChangedEventArgs e)
   在 GalaSoft.MvvmLight.ObservableObject.RaisePropertyChanged(String propertyName)
   在 GalaSoft.MvvmLight.ViewModelBase.RaisePropertyChanged[T](String propertyName, T oldValue, T newValue, Boolean 广播)
   在 GalaSoft.MvvmLight.ViewModelBase.Set[T](String propertyName, T& field, T newValue, Boolean broadcast)
   在 TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.set_Channel1(双值)
   在 TA.UWP.Devices.Samples.ViewModel.TemperatureSensorViewModel.HandleSensorValueChanged(对象发送者,SensorValueChangedEventArgs e)
   在 TA.UWP.Devices.MLX90614Thermometer.RaiseValueChanged(UInt32 通道,双值)
   在 TA.UWP.Devices.MLX90614Thermometer.SampleAllChannels()
   在 TA.UWP.Devices.MLX90614Thermometer.b__37_0()
   在 System.Threading.Tasks.Task.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
--- 从先前抛出异常的位置结束堆栈跟踪 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)
   在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   在 TA.UWP.Devices.MLX90614Thermometer.d__37.MoveNext()
--- 从先前抛出异常的位置结束堆栈跟踪 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)
   在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   在 TA.UWP.Devices.MLX90614Thermometer.d__38.MoveNext()

笏?我不明白这里发生了什么。谁能看到问题可能是什么?

4

2 回答 2

5

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

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

  • 重点学习:UWP 中的 XAML ≠ WPF在考虑 Universal Windows Apps 时,您不能依赖 WPF 文档

然后我发现这个问题与我的相似,特别是发帖人犯了我的错误,假设他正在处理 WPF。接受的答案让我想到了另一个关于 MVVM Light Toolkit 的DispatcherHelperclass的问题,它可用于将任何代码编组到调度程序线程上。

所以,看来我必须自己做线程编组(我真的很讨厌 Windows 编程的这方面,我希望微软能做出线程安全的 UI 技术!)。

所以我更新了我的属性以使用这种模式:

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

这现在似乎按预期工作。

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

于 2015-12-24T02:11:39.373 回答
2

我认为运行时不会对PropertyChanged事件进行任何形式的编组,诚然这有点烦人。

您可能想要执行以下操作:

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

使这发生在 UI 线程上。

于 2015-12-24T01:57:54.400 回答