Why Does Raising PropertyChanged events from a timer cause an COMException?

2

I'm developing a Universal Windows Platform app using XAML that runs on a Raspberry Pi under Windows 10 IoT Core. The app drives a temperature sensor that's on the I2C bus. The sensor class is MLX90614Thermometer. The sensor uses a DispatcherTimer to take readings every 100 milliseconds (approx) and updates a moving average. When the value of the moving average changes by more than a specified threshold, the sensor raises a ValueChanged event and provides the new value in the event args.

In my ViewModel class, TemperatureSensorViewModel, I subscribe to the sensor's ValueChanged event and use it to update bound properties named Ambient, Channel1 and Channel2. These properties are bound to text blocks in the XAML UI. Here is the event handler:

    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;
        }
    }

...and here is a sample data binding for Ambient...

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

I'm using the MVVM Light Toolkit, so my properties are implemented like this (only Ambient shown, but the others are identical except in name):

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

The MVVM Light Toolkit provides the Set() method, which automatically raises the PropertyChanged notification for the property being set.

This works correctly if I read a single sample from the sensor in response to a button press. As soon as I enable the automatic sampling mode (which is timer based) though, it starts throwing COMExceptions. So this must be some kind of threading issue related to the timer.

Now, if I understand correctly, the runtime is supposed to marshal PropertyChanged notifications onto the UI thread automatically; and that does seem to be the case from looking at the stack trace. However, I eventually get a COMException. Ugh.

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? I don't understand what's happening here. Can anyone see what the problem might be?

xaml
uwp
inotifypropertychanged
windows-10-iot-core
mvvm-toolkit
asked on Stack Overflow Dec 23, 2015 by Tim Long • edited Dec 24, 2015 by Tim Long

2 Answers

5

After further research, think I can answer my own question...

It seems that I made an invalid assumption about PropertyChanged events being marshalled to the UI thread automatically. I read that in a few places in articles about WPF, but as @Clemens pointed out in a comment, this is not WPF we are talking about, it is the Universal Windows Platform, which is a derivative of the Windows Runtime (WinRT).

  • Key Learning: XAML in UWP ≠ WPF. You can't rely on WPF documentation when considering Universal Windows Apps.

Then I found this question which has similarities to mine, specifically that the poster has made my mistake of assuming that he's dealing with WPF. The accepted answer led me to this other question regarding the MVVM Light Toolkit's DispatcherHelper class, which can be used to marshal any code onto the dispatcher thread.

So, it seems that I have to do my own thread marshalling (I really hate this aspect of Windows programming, I wish Microsoft would make a thread-safe UI technology!).

So I have updated my properties to use this pattern:

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

This now appears to work as expected.

I think a lot of people are going to fall into this quagmire so I am leaving this answer here in the hope that people will find it when they need it.

answered on Stack Overflow Dec 24, 2015 by Tim Long • edited May 23, 2017 by Community
2

I don't think the runtime does any sort of marshalling for PropertyChanged events, which is admittedly a bit irritating.

You probably want to do something like:

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

to make this happen on the UI thread.

answered on Stack Overflow Dec 24, 2015 by gregstoll

User contributions licensed under CC BY-SA 3.0