WPF 中 ViewModel 的异步 UI 更新

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17065810/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:59:05  来源:igfitidea点击:

asynchronous UI update from ViewModel in WPF

wpfmultithreadingmvvm-light

提问by Andrey Ant

I am having a problem with getting data from db and showing in UI asynchronously. I am using MVVM light, when I click the button, action is triggered in ViewModel:

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

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

At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done. At this point I need to update ListBox ItemSource. But when I try to update is I get “The calling thread cannot access this object because a different thread owns it”I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke()and different magic, but it still doesn't work.

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

I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.

我试图将 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.
        }
    }

I tried to change this line to

我试图将此行更改为

Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () =>this.FriendsList.View.Refresh())); - still the same.

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

I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods. Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.

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

I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.

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

A bit more details on how _redisModel.GetFriendsListAsync works: In the end(after all chain of calls) it ends up here:

关于 _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 
  }
 });

In viewmodel I have method:

在视图模型中,我有方法:

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

Can anybody please help me with this ? Thank you

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

采纳答案by sthotakura

You are creating CollectionViewSourcein one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompletedto

您正在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();
                     }));
        }
    }
}

回答by John Bowen

You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.

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

If you include the relevant code it would be easier to say for sure.

如果您包含相关代码,则更容易确定。