Question

I'm working on a WinRT/Windows Store app written C# that uses MVVM Light. I'm trying to modify MVVM Light's OnPropertyChanged() method so that Event notifications always occur on the UI thread, otherwise UI elements bound to my view model properties will miss the event notifications since they have to occur on the UI thread. Currently I am trying this code:

/// <summary>
/// Event handler for the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the property that has changed.</param>
protected void OnPropertyChanged(string propertyName = null)
{
    var eventHandler = PropertyChanged;
    if (eventHandler != null)
    {
        // Make sure the event notification occurs on the UI thread or the INotifyPropertyChanged
        //  notification will not be seen by the consumer of this event or worse, 
        //  a "wrong thread" COM Exception will be raised if we are not on the UI thread.
        var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;

        dispatcher.RunAsync(dispatcher.CurrentPriority,
            () =>
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            });
    }
}

I have used code like this before to push code on to the UI thread. However in this case, I get the following Exception:

A first chance exception of type 'System.Exception' occurred in Common_WinStore.DLL

WinRT information: The application called an interface that was marshalled for a different thread.

This is confusing since the point of CoreWindow.Dispatcher is to schedule the execution of code from a non-UI thread on to the UI thread. I read many of the marshaling error threads on SO, but none of them deal with getting a marshaling Exception when trying to call RunAsync() on the dispatcher itself. Instead they cover using the code I'm using to cure the occurrence of that Exception.

Why am I getting this Exception with Dispatcher.RunAsync() and how can I fix it?

CONTEXT NOTE: I got into this situation because of a deadlock situation with some code triggered by UI elements that are bound to WriteableBitmap properties in my view model. The await calls would deadlock when the async task awaited tried to continue on the original thread, which was the UI thread, and that thread was waiting on the call to complete. To solve this I added ConfigureAwait(false) to the await statement to free the call from the UI thread context. That led to a wrong thread COM Exception when MVVM Light's OnNotifyPropertyChanged() tried to update the property. That's why I'm trying to push that code back to the UI thread. I want to do this anyway because if I succeed, I don't have worry about event notifications happening properly, regardless of the calling context.

UPDATE: I added the AsyncEx library as suggested by Stephen Cleary. It works really well so far. I had a snag at first as you can see in my reply comment to his answer. The objects I have contain a JPEG bytes field that is deserialized into by the SQLite engine. That meant my constructor is parameterless and therefore initiating the async method to convert the JPEG bytes to a bindable WriteableBitmap object failed. This happened because the async conversion method began running before the bytes need for conversion had been deserialized.

I solved this with an AsyncManualResetEvent object that is in the unset state at first. The property setter for the JPEG bytes property sets the event. The async conversion method is waiting on this event object and therefore is released to do the conversion, immediately after the bytes are available. I am posting code excerpts below to see if Stephen and others of you see any potential problems with the implementation or if there's a simpler way to accomplish this:

    // Create an Async manual reset event for the JPEG conversion method in the unset state.
    private AsyncManualResetEvent _asyncManResetEvent = new AsyncManualResetEvent(false);


    /// <summary>
    /// The jpeg bytes for the JPEG image for the videomark.
    /// </summary>
    private byte[] _thumbnailJpegBytes;

    public byte[] ThumbnailJpegBytes
    {
        get
        {
            return this._thumbnailJpegBytes;
        }

        set
        {
            SetProperty(ref this._thumbnailJpegBytes, value, "ThumbnailJpegBytes");

            // Release the lock so that the async call waiting to convert the JPEG bytes to
            //  a WriteableBitmap can finish up.
            _asyncManResetEvent.Set();
        }
    }

// The WriteableBitmap property.
[Ignore]
public INotifyTaskCompletion<WriteableBitmap> ThumbnailAsync
{
    get;

    private set;
}

    // The async method passed to the NotifyTaskCompletion constructor.
async private Task<WriteableBitmap> ConvertJpegBytesToThumbnail()
{
    // Wait for data to be available.
    await this._asyncManResetEvent.WaitAsync();

    return await Misc.JpegBytesToBitmap(ThumbnailJpegBytes);    
}
Was it helpful?

Solution

In my opinion, this is the wrong solution for the problem. UI components (obviously) have UI-affinity. I would argue that anything data-bound to those UI components also have UI-affinity.

So, the core problem is that you're blocking in a data-bound property. This should be avoided anyway, particularly in Windows Store apps. I have a blog post that discusses using async properties (the last section covers data-binding).

In summary, you should use a type like NotifyTaskCompletion from my AsyncEx library to asynchronously initialize the property value. This solution provides two benefits: your application is responsive (not blocking), and the code updating the property is appropriately marshaled to the UI thread before raising the PropertyChanged event.

OTHER TIPS

I reproduced this problem and found the actual cause... it's in dispatcher.CurrentPriority.

This is a property resulting in a getter function that's executed synchronously on the original, parallel thread and not on the UI thread. Only the lambda code is executed asynchronously on the UI thread. And the getter calls into something that can't work from a different thread.

Replace this with CoreDispatcherPriority.Normal, which is a constant, and it'll work perfectly fine; there's no need for any 3rd party libraries or anything fancy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top