2012-07-24 37 views
2

我目前正在研究Windows Phone應用程序,我想使用Reactive Extensions創建異步以獲得更好的UI體驗。MVVM,ObservableCollection和Reactive Extensions(Rx)無效跨線程訪問

我使用MVVM模式:我的視圖有一個列表框在我的ViewModel綁定到一個ObservableCollection的項目。一個項目具有傳統屬性,如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> 

我實例我MainViewModel在App.xaml.cs:

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.Current.RootVisual.Dispatcher的App.xaml.cs。

public class MainViewModel : ViewModelBase 
{ 
    [...] 

    public Dispatcher CurrentDispatcher { get; set; } 

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

    [...] 
} 

但我想用Reactive Extentions。所以我嘗試了像ToAsync或ToObservable等Rx的不同元素,但是當我向清單中添加一個項目時,我有一些「UnauthorizedAccessException未處理」和「無效的跨線程訪問」。

我試圖ObserveOn其他線程可能錯誤來自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並在Susbscribe中使用它。這是完美的!非常感謝!

+0

你真的需要考慮重塑代碼來產生你的加載項作爲'IObservable ',然後對調度程序調度程序執行'ObserveOn()'。不要只在Rx代碼中顯示喇叭。試着讓它成爲你查詢的核心方式。 – Enigmativity 2012-07-25 00:15:23

+0

你是對的我想用我的起源方法使用Rx,這是一個壞主意。謝謝你的建議。 – 2012-07-27 02:55:00

回答

3

有似乎是在你的代碼中沒有異步(保存BeginInvoke的,我不認爲是做什麼的,你覺得是做)。

我假設你想使用的Rx因爲你的代碼是所有阻塞的時刻和用戶界面無響應,而數據庫的連接並且將數據加載:-(

要什麼做的是在後臺線程執行「繁重」,然後一旦你的價值觀只是將它們添加到調度員的ObservableCollection您可以採取以下三種方式這一個:

  1. 返回一個整個集合然後通過添加到ObservableCollection的foreach循環遍歷整個列表,這有阻塞UI的潛在缺點(不響應sive應用程序)如果列表太大
  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); 
       } 
       }); 
} 

該代碼對於單元測試並不理想,但它似乎並不是您感興趣的任何方式(靜態成員,具有數據庫連接的虛擬機等),所以這可能只是工作?!

+0

謝謝李我改造了一下你的代碼,它的工作完美!然後我已經知道IntroToRx.com,但我想我會再讀一遍。 ;) – 2012-07-27 02:56:34

+0

很高興這一切工作。接下來我正在考慮做IntroToRx v2,但最近我正在考慮寫一本關於MVVM的(好)書,我認爲這本書是最難理解的模式。 DDD + Rx + WPF =(好)MVVM :-) – 2012-08-15 12:54:44

0

我剛碰到這個。有多種方式可以到達調度員,我不得不使用以下方法。我直接從我的ViewModels中調用它(不從外部傳入)。

Application.Current.Dispatcher.Invoke(action); 
+0

這是不相關的,但你幾乎總是想要「BeginInvoke」而不是在這裏調用。 – 2012-07-25 12:54:43

+0

好的,謝謝你,但我的目標是真正使用Reactive Extensions。這是一個非常強大的框架。 – 2012-07-27 02:59:34

+0

在這種情況下,您想使用DispatcherScheduler。 – 2013-08-08 16:29:12

相關問題