C# MVVM、ObservableCollection和反应式扩展(Rx)的跨线程访问无效

C# MVVM、ObservableCollection和反应式扩展(Rx)的跨线程访问无效,c#,.net,mvvm,system.reactive,reactive-programming,C#,.net,Mvvm,System.reactive,Reactive Programming,我目前正在开发Windows Phone应用程序,我希望使用反应式扩展创建异步,以获得更好的UI体验 我使用MVVM模式:我的视图在我的视图模型中有一个列表框绑定到一个可观察的项目集合。项目具有传统属性,如名称或IsSelected <ListBox SelectionMode="Multiple" ItemsSource="{Binding Checklist, Mode=TwoWay}"> <ListBox.ItemTemplate> <

我目前正在开发Windows Phone应用程序,我希望使用反应式扩展创建异步,以获得更好的UI体验

我使用MVVM模式:我的视图在我的视图模型中有一个列表框绑定到一个可观察的项目集合。项目具有传统属性,如名称或IsSelected

<ListBox SelectionMode="Multiple" ItemsSource="{Binding Checklist, Mode=TwoWay}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
我在主页上加载我的数据

public partial class MainPage : PhoneApplicationPage
{
   public MainPage()
    {
        InitializeComponent();
        this.DataContext = App.ViewModel;
        this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
    }

    private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        App.ViewModel.LoadData();
    }
}
我从ViewModel加载数据(稍后我将把代码移到模型中):

我使用了一个名为CurrentDispatcher的属性,它在App.xaml.cs中用App.Current.RootVisual.Dispatcher填充

public class MainViewModel : ViewModelBase
{
    [...]

    public Dispatcher CurrentDispatcher { get; set; }

    public void GetData()
    {
        this.CurrentDispatcher.BeginInvoke(new Action(LoadData));
    }

    [...]
}
但我想使用被动扩展。因此,我尝试了Rx的不同元素,例如ToAsync或ToObservable,但当我将一个项目添加到清单中时,我遇到了一些“未经授权的访问异常未处理”和“无效的跨线程访问”

我试图观察其他线程,因为可能错误来自UI和后台线程之间的混合,但它不起作用。也许我不会像在那种特殊情况下那样使用Rx

任何帮助都将不胜感激

在回答后编辑:

这是一个非常有用的代码

public void GetData()
{
    Observable.Start(() => LoadData())
    .ObserveOnDispatcher()
    .Subscribe(list =>
    {
        foreach (Item it in list)
        {
            this.Checklist.Add(it);
        }
    });
}

public ObservableCollection<Item> LoadData()
{
    var results = new ObservableCollection<Item>();

    using (ItemDataContext context = new ItemDataContext(_connectionString))
    {
        //Loading

        var contextItems = from i in context.Items
                           select i;

        foreach (Item it in contextItems)
        {
            results.Add(it);
        }
    }

    return results;
}
public void GetData()
{
可观察的。开始(()=>LoadData())
.ObserveOnDispatcher()
.订阅(列表=>
{
foreach(将其列在列表中)
{
本.清单.添加(it);
}
});
}
公共ObservableCollection LoadData()
{
var结果=新的ObservableCollection();
使用(ItemDataContext上下文=新的ItemDataContext(_connectionString))
{
//装载
var contextItems=来自context.Items中的i
选择i;
foreach(在contextItems中输入它)
{
结果:添加(it);
}
}
返回结果;
}

如你所见,我以前没有正确使用。现在我可以使用ObservableCollection并在SUSubscribe中使用它。太完美了!非常感谢

我自己也遇到了这个。有多种方法可以到达调度程序,我必须使用以下方法。我直接从ViewModels中调用它(不从外部传入)


您的代码中似乎没有异步性(保存BeginInvoke,我不认为它正在做您认为它正在做的事情)

我假设您希望使用Rx,因为您的代码目前全部阻塞,并且在连接到数据库并加载数据时UI没有响应:-(

您要做的是在后台线程上执行“重载”,然后在获得值后将其添加到Dispatcher上的ObservableCollection中。您可以通过以下三种方式之一执行此操作:

  • 一次性返回整个集合。然后,通过添加到ObservableCollection的foreach循环遍历列表。如果列表太大,则可能会阻止UI(无响应的应用程序)
  • 每次返回一个集合值,在对dispatcher的独立调用中将每个项添加到ObservableCollection中。这将使UI保持响应,但可能需要更长的时间才能完成
  • 以缓冲块的形式返回集合,并尝试充分利用这两个方面
  • 您想要的代码可能如下所示

    public IList<Item> FetchData()
    {
      using (ItemDataContext context = new ItemDataContext(_connectionString))
      {
        //....
        var results = new List<Item>();
        foreach (Item it in contextItems)
        {
          results.Add(it);
        }
        return results;
      }
    }
    public void LoadData()
    {
      Observable.Start(()=>FetchData())
                .ObserveOnDispatcher()
                .Subscribe(list=>
                  {
                    foreach (Item it in contextItems)
                    {
                      this.Checklist.Add(it);
                    }
                  });
    }
    
    public IList FetchData()
    {
    使用(ItemDataContext上下文=新的ItemDataContext(_connectionString))
    {
    //....
    var results=新列表();
    foreach(在contextItems中输入它)
    {
    结果:添加(it);
    }
    返回结果;
    }
    }
    公共void LoadData()
    {
    可观察的。开始(()=>FetchData())
    .ObserveOnDispatcher()
    .订阅(列表=>
    {
    foreach(在contextItems中输入它)
    {
    本.清单.添加(it);
    }
    });
    }
    

    该代码对于单元测试并不理想,但您似乎对它不感兴趣(静态成员、具有DB连接的VM等),因此这可能会起作用?!

    您确实需要重新设计代码,以将加载的项生成为
    IObservable
    ,然后执行
    ObserveOn()
    发送给调度程序。不要只在Rx代码中显示horn。尝试将其作为您查询的核心方式。您是对的,我想将Rx与我的origin方法一起使用,这是个坏主意。感谢您的建议。这与此无关,但您几乎总是希望“BeginInvoke”而不是在这里调用。好的,谢谢,但我的目标是真正使用反应式扩展。这是一个非常强大的框架。在这种情况下,您需要使用DispatchersScheduler。谢谢Lee,我重新修改了您的代码,它工作得很好!那时我已经知道IntroToRx.com了,但我想我会再看一遍。;)很高兴一切都成功了。我想下一步做介绍,但最近我想写一本关于MVVM的(好)书,我认为这是最误解的模式。DDD+Rx+WPF=(良好)MVVM:-)
    public class MainViewModel : ViewModelBase
    {
        [...]
    
        public Dispatcher CurrentDispatcher { get; set; }
    
        public void GetData()
        {
            this.CurrentDispatcher.BeginInvoke(new Action(LoadData));
        }
    
        [...]
    }
    
    public void GetData()
    {
        Observable.Start(() => LoadData())
        .ObserveOnDispatcher()
        .Subscribe(list =>
        {
            foreach (Item it in list)
            {
                this.Checklist.Add(it);
            }
        });
    }
    
    public ObservableCollection<Item> LoadData()
    {
        var results = new ObservableCollection<Item>();
    
        using (ItemDataContext context = new ItemDataContext(_connectionString))
        {
            //Loading
    
            var contextItems = from i in context.Items
                               select i;
    
            foreach (Item it in contextItems)
            {
                results.Add(it);
            }
        }
    
        return results;
    }
    
    Application.Current.Dispatcher.Invoke(action);
    
    public IList<Item> FetchData()
    {
      using (ItemDataContext context = new ItemDataContext(_connectionString))
      {
        //....
        var results = new List<Item>();
        foreach (Item it in contextItems)
        {
          results.Add(it);
        }
        return results;
      }
    }
    public void LoadData()
    {
      Observable.Start(()=>FetchData())
                .ObserveOnDispatcher()
                .Subscribe(list=>
                  {
                    foreach (Item it in contextItems)
                    {
                      this.Checklist.Add(it);
                    }
                  });
    }