【问题标题】:asynchronous UI update from ViewModel in WPFWPF 中 ViewModel 的异步 UI 更新
【发布时间】:2013-06-08 14:00:39
【问题描述】:

我在从 db 获取数据并在 UI 中异步显示时遇到问题。 我正在使用 MVVM 灯,当我单击按钮时,会在 ViewModel 中触发操作:

    private void SearchQuery(string query)
    {
        _redisModel.GetFriendsListAsync(query);
    } 

在某些时候 GetFriendsListCompleted 被后台线程调用,通知 viewmodel 工作已完成。 此时我需要更新 ListBox ItemSource。但是当我尝试更新时,我得到了 “调用线程无法访问此对象,因为不同的线程拥有它” 我试过 Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() 和其他魔法,但它仍然不起作用。

我尝试将 UI 调度程序提供给 ViewModel,然后从那里调用它 - 没有用。

private string filterText = string.Empty;
    public string FilterText
    {
        get { return filterText; }
        set
        {
            filterText = value;
            this.RaisePropertyChanged(() => this.FilterText);

            this.FriendsList.View.Refresh(); // Here where exception is happening.
        }
    }

我试图将这一行改为

Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () =>this.FriendsList.View.Refresh())); - 还是一样。

我正在使用 Telerik ListBox 来显示项目。 FriendList 是 CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html)。当我使用 WPF 控件示例中的 Telerik 示例时,它可以工作。当我使用我的异步方法时,问题开始出现。 视图类型为 System.ComponentModel.ICollectionView,用于过滤和分组。

我也尝试将 ObservableCollection 分配给 ListBox 的 Items 属性,但它也不起作用。

关于 _redisModel.GetFriendsListAsync 如何工作的更多细节: 最后(在所有调用链之后)它在这里结束:

public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
   _cbMethod = cbMethod;
   _state = state;
   QueueWorkOnThreadPool(workToBeDone);
}

ThreadPool.QueueUserWorkItem(state =>
{
  try
  {
     _result = workToBeDone();
  }
  catch (Exception ex)
  {
       _exception = ex;
  }
  finally
  {
     UpdateStatusToComplete(); //1 and 2 
     NotifyCallbackWhenAvailable(); //3 callback invocation 
  }
 });

在视图模型中我有方法:

private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
    {
        if (!e.HasError)
        {
            var curr = e.Results;
            if (curr != null)
            {
                this.FriendsList= new CollectionViewSource();

                this.FriendsList.Source = list;
                this.FriendsList.Filter += this.FriendFilter;
                FilterText = "";

                Dispatcher.Invoke(DispatcherPriority.Normal, new Action(

                        () => this.FriendsList.View.Refresh()));
            }
    }

有人可以帮我解决这个问题吗? 谢谢

【问题讨论】:

  • 编辑您的问题并为您的问题添加一些上下文。 FriendsList 的类型是什么。您从 VM 访问的 View 属性是什么?

标签: wpf multithreading mvvm-light


【解决方案1】:

您正在一个线程中创建CollectionViewSource 并在另一个线程(调度程序线程)中刷新它。将您的 GetFriendsListCompleted 更新为

private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
    if (!e.HasError)
    {
        var curr = e.Results;
        if (curr != null)
        {
            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
                    () => {
                     this.FriendsList= new CollectionViewSource();
                     this.FriendsList.Source = list;
                     this.FriendsList.Filter += this.FriendFilter;
                     FilterText = "";
                     this.FriendsList.View.Refresh();
                     }));
        }
    }
}

【讨论】:

  • 太棒了!有效!非常感谢。但目前我正在将 App.Current.Dispatcher 传递给 CodeBehind 内的 ViewModel。有没有更优雅的方法?
  • @Andy 请检查接受的答案this question
  • 我试过了,还是不行。在我向 ViewModel 构造函数添加新参数后,它失败了。 MEF 就是找不到。这很奇怪,因为我已经有了一个参数——我的 DAL 组件并且它正在工作。我认为这与在 UI 中声明的 WpfContext 类和初始化顺序有关。
  • @Andy 从未使用过 MEF,因此无法帮助您解决这个问题。您可能想将其作为一个新问题发布,有人可以帮助您。
【解决方案2】:

您没有显示完成时在后台线程上实际运行的任何代码,但我猜您正在其中创建一个集合对象,然后您尝试将其分配给您的 CollectionView。当 CV 尝试从您的 Refresh 调用更新(在 UI 线程上)时,它会尝试使用其他线程拥有的集合。

如果您包含相关代码,肯定会更容易说。

【讨论】:

  • 你的猜测相当准确。我已经用更多代码更新了问题。
猜你喜欢
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多