MVVM、ObservableCollection和Reactive Extensions(Rx)的跨线程访问无效



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

我使用MVVM模式:我的视图在ViewModel中有一个ListBox绑定到ObservableCollection of Items。项具有传统属性,如Name或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>

我在App.xaml.cs:中实例化我的MainViewModel

public partial class App : Application
{
    private static MainViewModel _viewModel = null;
    public static MainViewModel ViewModel
    {
        get
        {
            if (_viewModel == null)
                _viewModel = new MainViewModel();
            return _viewModel;
        }
    }
    [...]
}

我在MainPage_Loaded中加载我的数据。

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加载数据(稍后我将把代码移到模型中):

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<Item> _checklist;
    public ObservableCollection<Item> Checklist
    {
        get
        {
            return this._checklist;
        }
        set
        {
            if (this._checklist != value)
            {
                this._checklist = value;
            }
        }
    }
    private const string _connectionString = @"isostore:/ItemDB.sdf";
    public void LoadData()
    {
        using (ItemDataContext context = new ItemDataContext(_connectionString))
        {
            if (!context.DatabaseExists())
            {
                // Create database if it doesn't exist
                context.CreateDatabase();
            }
            if (context.Items.Count() == 0)
            {
                [Read my data in a XML file for example]
                // Save changes to the database
                context.SubmitChanges();
            }
            var contextItems = from i in context.Items
                                select i;
            foreach (Item it in contextItems)
            {
                this.Checklist.Add(it);
            }
        }
    }
    [...]
}

它运行良好,项目在视图中更新。

现在我想创建异步。在我从View而不是LoadData调用的新方法中使用传统的BeginInvoke,它可以很好地工作。

public partial class MainPage : PhoneApplicationPage
{
    [...]
    private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        App.ViewModel.GetData();
    }
}

我使用了一个名为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,但当我向清单中添加项目时,我遇到了一些"UnauthorizedAccessException未处理"one_answers"无效跨线程访问"。

我试着观察其他线程,因为错误可能来自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;
}

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

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

我假设你想使用Rx,因为你的代码目前都处于阻塞状态,并且在连接到数据库并加载数据时UI没有响应:-(

您要做的是在后台线程上执行"繁重的工作",然后在获得值后,将它们添加到Dispatcher上的ObservableCollection中。你可以用三种方法之一:

  1. 一次性归还全部藏品。然后循环列表,foreach循环添加到ObservableCollection中。如果列表太大,这可能会阻止UI(无响应的应用程序)
  2. 每次返回集合一个值,在对调度器的独立调用中将每个项添加到ObservableCollection。这将使UI保持响应,但可能需要更长的时间才能完成
  3. 以缓冲块的形式返回集合,并尝试两全其美

您想要的代码可能看起来像这个

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

该代码不适合单元测试,但您似乎对它不感兴趣(静态成员、具有DB连接的VM等),所以这可能只是工作?!

我自己刚刚遇到这个问题。有多种方法可以到达调度员,我不得不使用以下方法。我直接从ViewModels调用它(不从外部传入)。

Application.Current.Dispatcher.Invoke(action);

相关内容

  • 没有找到相关文章

最新更新