【发布时间】:2014-02-04 14:46:52
【问题描述】:
我正在开发一个使用 MVVM Light 编写的 C# 编写的 WinRT/Windows 应用商店应用程序。我正在尝试修改 MVVM Light 的 OnPropertyChanged() 方法,以便事件通知始终发生在 UI 线程上,否则绑定到我的视图模型属性的 UI 元素将错过事件通知,因为它们必须发生在 UI 线程上。目前我正在尝试这段代码:
/// <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));
});
}
}
我以前使用过这样的代码将代码推送到 UI 线程。但是在这种情况下,我得到以下异常:
“System.Exception”类型的第一次机会异常发生在 Common_WinStore.DLL
WinRT 信息:应用程序调用的接口是 为不同的线程编组。
这很令人困惑,因为 CoreWindow.Dispatcher 的目的是将代码的执行从非 UI 线程调度到 UI 线程。我在 SO 上阅读了许多封送处理错误线程,但没有一个处理在尝试调用调度程序 本身上的 RunAsync() 时获得封送处理异常。相反,他们使用了我用来解决该异常发生的代码。
为什么我会在 Dispatcher.RunAsync() 中遇到此异常,我该如何解决?
上下文注意:我遇到这种情况是因为一些代码由绑定到我的视图模型中的 WriteableBitmap 属性的 UI 元素触发的死锁情况。当等待的 async 任务尝试在原始线程(即 UI 线程)上继续,并且该线程正在等待调用完成时,await 调用将死锁。为了解决这个问题,我在 await 语句中添加了 ConfigureAwait(false) 以从 UI 线程上下文中释放调用。当 MVVM Light 的 OnNotifyPropertyChanged() 尝试更新属性时,这会导致 错误的线程 COM 异常。这就是我试图将该代码推回 UI 线程的原因。无论如何我都想这样做,因为如果我成功了,我不必担心事件通知会正确发生,无论调用上下文如何。
更新:我按照 Stephen Cleary 的建议添加了 AsyncEx 库。到目前为止它工作得非常好。正如您在我对他的回答的回复评论中看到的那样,我起初遇到了障碍。我拥有的对象包含一个 JPEG 字节字段,该字段由 SQLite 引擎反序列化。这意味着我的构造函数是无参数的,因此启动异步方法将 JPEG 字节转换为可绑定的 WriteableBitmap 对象失败。这是因为异步转换方法在需要转换的字节被反序列化之前开始运行。
我首先使用处于未设置状态的 AsyncManualResetEvent 对象解决了这个问题。 JPEG 字节属性的属性设置器设置事件。异步转换方法正在等待此事件对象,因此在字节可用后立即释放以进行转换。我在下面发布代码摘录,以查看 Stephen 和你们中的其他人是否发现实施中存在任何潜在问题,或者是否有更简单的方法来完成此操作:
// 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);
}
【问题讨论】:
-
前段时间你在错误的线程上创建了一个非敏捷对象时出错了。 Windows 给了它一个安全的家,另一个线程,可以确保它以线程安全的方式使用。该对象订阅了 PropertyChanged 事件。它在 UI 线程上得到了回调,但这并不是它真正满意的线程。它希望在自己的线程上进行回调。找到该对象并在您的主 UI 线程上创建它是您需要处理的解决方案。也解决了这个死锁问题。
-
HansPassant 消费者是通过数据绑定访问属性的 UI 元素。我无法更改运行对象的线程。
标签: c# windows-store-apps async-await mvvm-light deadlock