【问题标题】:Why Does Raising PropertyChanged events from a timer cause an COMException?为什么从计时器引发 PropertyChanged 事件会导致 COMException?
【发布时间】:2016-03-30 07:45:53
【问题描述】:

我正在使用 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;
        }
    }

...这里是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()

什么?我不明白这里发生了什么。谁能看出问题出在哪里?

【问题讨论】:

  • 请注意,UWP/Windows 运行时和 WPF 是两个不同的框架。您的问题与 WPF 无关
  • 建议@Tim:我认为你在寻找winrt-xaml,但我认为如果你使用两个标签,你的曝光会更好:uwp + xaml

标签: xaml uwp inotifypropertychanged windows-10-iot-core mvvm-toolkit


【解决方案1】:

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

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

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

然后我发现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());
        }
    }

现在看来可以按预期工作了。

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

【讨论】:

  • 关键句:XAML in UWP ≠ WPF
【解决方案2】:

我认为运行时不会对 PropertyChanged 事件进行任何形式的编组,这确实有点令人讨厌。

你可能想做这样的事情:

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

在 UI 线程上实现这一点。

【讨论】:

    猜你喜欢
    • 2021-02-18
    • 2010-12-07
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多