【问题标题】:Getting an interface was marshaled for a different thread Exception with UI thread dispatcher call?使用 UI 线程调度程序调用为不同的线程编组获取接口?
【发布时间】: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


【解决方案1】:

在我看来,这是解决问题的错误方法。 UI 组件(显然)具有 UI 亲和性。我认为任何绑定到这些 UI 组件的数据也具有 UI 亲和性。

因此,核心问题是您阻塞了数据绑定属性。无论如何都应该避免这种情况,尤其是在 Windows 应用商店应用程序中。我有一篇博客文章讨论了使用 async properties (the last section covers data-binding)

总之,您应该使用我的 AsyncEx 库中的 NotifyTaskCompletion 之类的类型来异步初始化属性值。此解决方案有两个好处:您的应用程序是响应式的(不是阻塞的),并且更新属性的代码在引发 PropertyChanged 事件之前被适当地编组到 UI 线程。

【讨论】:

  • 谢谢。您的文章是我一直在阅读的一些文章,并且我已将 AsynEx 添加为书签。我会试一试并报告。
  • 我遇到了一个实施问题。当对象从 SQLite 数据库反序列化时,我正在从无参数构造函数中触发 NotifyTaskCompletion.Create() 调用。这是一个问题,因为对象中不存在异步调用所需的数据,所以我得到了 NullReferenceException。你能想出一种优雅的方式来延迟异步调用,直到对象被 SQLite 引擎完全反序列化?我希望避免等待 TaskEx.Delay() 循环在继续之前检查字节是否存在。
  • @RobertOschler:想到的第一个选项是将数据库数据与您的 ViewModel 分开。我自己做了非常简单的“模型”层,但我还没有从数据库中反序列化 ViewModels...
  • 请看看我的最新更新。我现在可以使用它,我想知道我的实现是否合理。非常感谢关于 AsyncExc 的提示。
  • @RobertOschler:这可行,但我不会那样做。我仍然认为将类型分开会更干净。不需要[Ignore] 等。毕竟,“视图模型”应该是 UI 当前状态的逻辑等价物,将 UI 状态序列化到 DB 几乎没有意义。
【解决方案2】:

我重现了这个问题并找到了真正的原因……它在 dispatcher.CurrentPriority 中。

这是一个导致 getter 函数的属性,该函数在原始并行线程上而不是在 UI 线程上同步执行。只有 lambda 代码在 UI 线程上异步执行。并且 getter 调用了无法从不同线程工作的东西。

用 CoreDispatcherPriority.Normal 替换它,它是一个常数,它会工作得很好;不需要任何 3rd 方库或任何花哨的东西。

【讨论】:

  • 谢谢。您是否知道您发现的问题是否仅适用于 WinRT/Windows 商店应用程序,或者这是否也与 Windows 8.1+ 桌面应用程序或 Windows 10 通用应用程序有关?
  • 您好,很抱歉回答晚了 - 我有一段时间没有查看这个网站了。我在 Windows 10 上的 UWP 应用中重现了这一点。
猜你喜欢
  • 1970-01-01
  • 2011-04-05
  • 2018-05-21
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多