Note: Forums will be making significant UX changes to address key usability improvements surrounding search, discoverability and navigation. To learn more about these changes please visit the announcement which can be found HERE.
Migrating this application architecture to RX

已答复 Migrating this application architecture to RX

  • 2012年2月29日 上午 01:53
     
     

    Hello,

    I need the advice of RX experts on this.

    I have a non-RX WPF application that communicates with a WCF service with callbacks (dual channel).
    Initially, the application loads a lot of data from the service and stores it in an ObservableCollection (let's call it AppData).
    Then, one or more windows are opened with ViewModels that all use the same AppData collection in one way or another.

    When the WCF client receives data (via callback or not) from the WCF service, it uses the WPF Dispatcher to modify AppData (add,update,delete) accordingly. This way, the UI gets updated, the collection access is synchronized, and no exceptions are thrown. This is normal stuff.

    There are some drawbacks to this approach. Sometimes, i would like to launch asynchronous tasks that also use AppData,
    but I'm forced to use the Dispatcher thread in case the collection gets modified while being used.

    Then comes RX. I began experimenting with RX and I really like it. I made an RX version of my WCF client using FromAsyncPattern
    and it rocks. But, as soon as I get data back from the service, I must switch the context to the Dispatcher thread using ObserveOn,
    then modify my collection. Same thing goes on for asynchronous tasks. The same problem still exists.

    What I would like to know, is if there are some better practices to access the same data with read and write operations using RX.
    I have the strong feeling that my old architecture could be replaced with something that better follows the RX philosophy.


    Regards

    Louis-Pierre Beaumont

所有回覆

  • 2012年2月29日 下午 02:11
     
     已答覆 包含代碼

    Hi Louis-Pierre,

    > Initially, the application loads a lot of data from the service and stores it in an ObservableCollection (let's call it AppData).

    Do you mean a one-time-only operation when the application starts?

    > Then, one or more windows are opened with ViewModels that all use the same AppData collection in one way or another.

    Are the various uses all related to binding data to UI controls?  If not, could you be more specific?

    Is the AppData collection a reference to the same object or does each ViewModel contain a different AppData instance, each of which are initialized to the same data?

    > the WCF client [snip] uses the WPF Dispatcher to modify AppData (add,update,delete) accordingly.
    > This way, the UI gets updated. [snip] This is normal stuff.

    I agree it seems normal.  Here's what the flow of data looks like so far:

    { } = Object
    [ ] = Collection
    | | = Marshaler

    {WCF Client} -> | Dispatcher | -> [ViewModels] -> [AppDatas] -> [UI Controls]

    Is this diagram correct?

    > Sometimes, i would like to launch asynchronous tasks that also use AppData, but I'm forced to use the Dispatcher thread [snip]

    What do you mean by "use AppData"?  Do these tasks modify the collection or do they simply read from it?

    The former: {ViewModel} -> [Tasks] -> [AppDatas]

    The latter: {ViewModel} -> [Tasks] <- [AppDatas]

    If it's the latter, then depending upon the size of the data and the length of the operations it might make sense to copy the data that you need into a new collection per task.  If the result of a task is used by the ViewModel and synchronization is a concern, then you can have the UI buffer changes until all tasks report completion, at which point AppData is updated with the latest changes from the service.  This of course depends on what kind of changes you expect and whether it's allowable for background operations to work on potentially stale data.  There may be a better approach though, as described below.

    > I made an RX version of my WCF client [snip] using ObserveOn [snip] The same problem still exists.

    Agreed, if you must keep all changes to AppData synchronized for binding to UI controls, then it makes sense to use ObserveOnDispatcher.  However, it doesn't help asynchronous tasks.

    > better practices to access the same data with read and write operations using RX. [snip]

    I'm still not entirely clear on your requirements, so it's hard to answer your question directly.  So far, my understanding is this:

    {WCF Client} -> | Dispatcher | -> [ViewModels] -> [AppDatas] -> [UI Controls]
    {ViewModel} -> [Tasks] -> [AppDatas]  -or-  {ViewModel} -> [Tasks] <- [AppDatas]  -or-  Both

    If this diagram is correct, then I don't think there's much you can do to avoid executing tasks on the UI thread, other than my previous suggestions.

    However, if the following diagram more accurately reflects your requirements:

    {WCF Client}+[AppDatas] -> | Dispatcher | -> [ViewModels] -> [UI Controls]
    {WCF Client}+[AppDatas] -> [Tasks]

    then it would seem that your application can make use of Rx to achieve a reactive model for UI bindings and tasks.

    Rx allows you to easily publish data to be shared by multiple observers, so if these are your requirements, then consider doing something like the following.

    IObservable<AppData> newData = client.NewDataOverPersistentConnection(...);
    
    IConnectableObservable<AppData> newDataShared = newData.Publish();
    
    newDataShared.ObserveOnDispatcher().Subscribe(UpdateViewModels);
    newDataShared.Subscribe(LaunchGlobalTasks);
    
    newDataShared.Connect();
    
    this.NewData = newDataShared;
    
    ...
    
    ViewModel_UIEventOrCommand(object sender, RoutedEventArgs e)
    {
    	MyService.Instance.NewData
    		.SelectMany(RunTask)
    		.ObserveOnDispatcher()
    		.Subscribe(UpdateUI);
    }

    Note that Rxx offers several features related to collecting data reactively and updating WPF bindings reactively.  For example, ListSubject can be generated directly from a query and it behaves just like ObservableCollection when used as the target of bindings.  Take a look at the "UI" category in the Rxx labs application for working examples.

    - Dave


    http://davesexton.com/blog

  • 2012年2月29日 下午 03:35
     
     

    Hi Dave,

    First of all, thank you for this complete and detailed answer.

    I'll answer your last statements first:

    > However, if the following diagram more accurately reflects your requirements:
    > {WCF Client}+[AppDatas] -> | Dispatcher | -> [ViewModels] -> [UI Controls]
    > {WCF Client}+[AppDatas] -> [Tasks]

    Yes, this looks like my requirements. I looked at ListSubject and I think it would be a perfect replacement
    for my ObservableCollection. I'm going to give it a try! :) I'll also post my results here.


    Here are some other clarifications:

    > Do you mean a one-time-only operation when the application starts?
    Yes

    > What do you mean by "use AppData"?  Do these tasks modify the collection or do they simply read from it?
    They simply read from it.
    The only place where the collection is modified is after a WCF service method call or callback (an item in the collection needs to be replaced, added or deleted)


    > Are the various uses all related to binding data to UI controls?  If not, could you be more specific?
    Some Views bind directly to AppData (ItemsSource=AppData).
    I also have ViewModels that perform linq queries on AppData (to select a subset), then register event handlers for NotifyCollectionChanged.

    > Is the AppData collection a reference to the same object [snip]...
    It is a reference to the same object.

    Again, thank you for this professional answer. Looks like there is a lot of solutions to common problems in your Rxx library.

    Louis-Pierre Beaumont

  • 2012年2月29日 下午 04:11
     
     

    Hi,

    Check out the Collect extensions in Rxx.  Specifically, the ones that have existing and changes parameters.  When the app starts you can make an intial request and pass that as the existing argument.  Also create an observable for your WCF callback that converts notifications into CollectionModification objects and pass that sequence as the changes argument.  The result is a ReadOnlyListSubject that is read-only in your view models and tasks and is only mutable by the changes sequence.  You can bind ItemsSource directly to this object and the UI will be updated automatically.

    - Dave


    http://davesexton.com/blog

    • 已編輯 Dave Sexton 2012年2月29日 下午 04:11 Added hyperlink
    •  
  • 2012年2月29日 下午 08:14
     
     

    Hello,

    I replaced my ObservableCollection with a ListSubject and removed all ObserveOn calls inside my WCF client.
    It works like a charm, and the WPF Dispatcher thread is free most of the time! :)

    My ViewModels now register an event handler for NotifyCollectionChanged with FromEventPattern and ObserveOnDispatcher.
    This makes a lot more sense.

    I will also follow your advice with the Collect extensions for my tasks.

    Thank you very much, now my application has the power to be fully reactive!

    Louis-Pierre Beaumont


  • 2012年2月29日 下午 08:34
     
     

    Hi, 

    > My ViewModels now register an event handler for NotifyCollectionChanged with FromEventPattern and ObserveOnDispatcher.

    Rxx provides a Subscription binding that does this for you, if your only goal is to push updates to UI controls.  You can use Subscription to replace Binding anywhere in XAML.  It supports binding to properties that return IObservable<T> directly, as well as ListSubject and related types.

    Here's an example lab:

    XAML
    http://rxx.codeplex.com/SourceControl/changeset/view/65438#1096340

    Code
    http://rxx.codeplex.com/SourceControl/changeset/view/65438#1096339

    - Dave


    http://davesexton.com/blog

  • 2012年2月29日 下午 08:40
     
     

    Hi,

    You may also be interested in using Rxx.ViewModel as your base type.  It's designed for tying the lifetime of observable subscriptions to elements' load/unload events.  You can also use the View.Model attached property in XAML, regardless of whether your view models derive from Rxx.ViewModel or not.  If you're using a different base type already, then you can implement IViewModel yourself (or not, it's optional.)

    Here's a lab:

    XAML
    http://rxx.codeplex.com/SourceControl/changeset/view/65438#1096710

    Code
    http://rxx.codeplex.com/SourceControl/changeset/view/65438#1096709

    (Sorry that this is turning into an Rxx-advert thread ;)

    - Dave


    http://davesexton.com/blog

  • 2012年3月1日 上午 02:21
     
     

    Hi again,

    That View.Model attached property is appealing. I need to investigate it's factory parameter a bit more. I need to pass a lot of parameters to my ViewModels constructors.

    One more little question about ListSubject:

    By looking at it's source, I noticed that it uses the lock keyword as a synchronization mechanism. Because of that lock, multiple read operations will never happen at the same time, right?

    Would it be bad if I replace the lock(gate) statements it uses with a multiple reader single writer lock mechanism such as ReaderWriterLockSlim?
    To be honest, I did it already,  but I think a modification like that requires your advice.

    Louis-Pierre Beaumont



  • 2012年3月1日 下午 04:22
     
     

    Hi,

    > I need to pass a lot of parameters to my ViewModels constructors.

    You can attach the view model in code via the SetViewModel extension method to supply your own constructor arguments.   See the following Silverlight lab as an example.  Note that the view model is attached in the control's constructor before InitializeComponent, though the view model has a parameterless constructor.  The attached property isn't available in Silverlight so this is the only way to do it.

    http://rxx.codeplex.com/SourceControl/changeset/view/65472#1126180

    > Because of that lock, multiple read operations will never happen at the same time, right?

    Do you mean in the observable returned by the View method?  Notifications will never be observed concurrently regardless of the lock, due to the serialized behavior that Rx guarantees (see §4.2 in the Rx Design Guidelines).

    > Would it be bad if I replace the lock(gate) statements it uses with a multiple reader single writer lock
    > mechanism such as ReaderWriterLockSlim?

    I'm not sure that it will actually change the behavior at all.  Where are you acquiring reader locks?

    - Dave


    http://davesexton.com/blog