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