none
Best Way To Wait For Multiple Loaded Events

    Question

  • I have data that needs to be returned in a couple of calls to the RIA server. I call both the methods and then wait for them to trigger the Loaded event. However, there doesn't seem a built in way to check which method call resulted in the Loaded event. Am I missing something? I can't rely on checking which entities have been loaded, because there may be zero entities in the data.

    What I'd like to do is set a IsBusy flag based on whether the calls have returned or not. To work around it I've created my own counter to check that the correct number of calls returns and passed tags in the UserState.

    I suppose I could split the two calls into a different class and aggregate it somehow, but they really both belong in the same ViewModel.

    Anyone have any tips?

     

    Cheers,

    James.

    Monday, April 20, 2009 5:37 AM

Answers

  • Along the lines of what the original post was looking for, DomainContext does expose an IsLoading property which will be true only when there are outstanding queries being executed. Perhaps that is what you were looking for with the IsBusy flag you mentioned?

    On our list of features we want to support query batching, which would allow you to specify a set of load/query operations to execute all together as a single server request. That would result in a single Loaded event when the query batch was complete. So think of something like context.Load(query1, query2, ...) where the arguments are a new type we're considering which represents a query request : something like (DomainContext.Load(params EntityQuery[] queries).

     

    Tuesday, April 21, 2009 7:52 PM

All replies

  • I would approach it similar to your IsBusy idea.  If the application can only have one submission event at a time, then creating an ActiveProcess counter would be the way to go.  Set it to 2 on the call, then decrement it when each call is returns and check to see if the counter is set to zero.

     If the application can have multiple processes running at once, then I have used a modified approach from above with a dictionary.  I used a <Guid, int> dictionary and assigned each group of processes a Guid that I passed.  Then store the same counter in the dictionary, basically giving you multiple counters per process.  Don't forget to remove the counter from the dictionary when it zero's out, so you don't have a memory leak of sorts.

    The other option to the above is to not run the calls in parallel.  Place the call to the second service in the completed event handler of the first.

     Hope this helps a bit.

    Tony

    Monday, April 20, 2009 9:07 AM
  • The Load methods all take a userState object. Just pass in a string saying which load you are calling and then check it on the loaded event.

    Monday, April 20, 2009 10:30 AM
  • Yep, been doing a combination of both od those things, just felt a bit wrong and wondered if there was some better method OTB.

    Considered serlializing the calls, but seems a bit of a waste seeing as they happen async.

    I'll continue as I was then.

    Monday, April 20, 2009 11:12 AM
  • I think using a state object is pretty much the accepted way to do this when doing async operations, so that is the OTB way of doing it. However, you could make it a bit more robust. For example, you could have a tracking object that is waiting for both loads to complete. Instead of passing a string tag into the load method you could pass an Action with a delegate from the tracking object (i.e. new Action(trackingObject.Operation1Complete)) which you then invoke from the Loaded event. You can also inherit your DataContext and build the tracking into the DataContext in whatever way you want.

    Monday, April 20, 2009 11:41 AM
  • Code Smell warning <g>: All I do is in a single Context.Loaded event handler, and before I process any of these requested EntityTypes, I check that the count for an Entity I wish to deal with is > 0, i.e., if (Context.<EntityType>.Count > 0). So I can rack 'em and stack 'em, and a single Loaded event safely handle each as it comes back. I had thought about call counts at first, but that did not guarantee that I was dealing with the correct (of multiples) Entity in the handler. Funny, I was unaware of the userState availability. I suppose I'll have to drill into that. Blame it on the documentation <g>.

    Monday, April 20, 2009 12:04 PM
  • If you want more control over which method is completed etc, you can use WCF. Beacuse a DomainService only has one Loaded event.. As some people mention the userState can be used.. but I shouldn't depend on the userState.. it will for example make sure you can end up with a big condition statment, which will not be fun to maintain..

    Tuesday, April 21, 2009 1:58 AM
  • @Bob yeah I went that way but realised that some of my entity lists could be empty, hence turning to counts Smile

    @Frederik I agree about the big condition statement, but RIA gives me too much for free to abandon it for WC.

    For the moment I'm going to keep it simple and use userstate but maybe breaking it up into smaller user controls each loading their own data might be the way? I'll investigate.

    Tuesday, April 21, 2009 4:23 AM
  • Could someone please provide a short example of using userState in a client Loaded event handler? Thanks.

    And yes, I finally just ran into a situation where my counts could be zero, and I needed to respond to that possibility (which is not an error state) as well.

    Tuesday, April 21, 2009 5:21 AM
  • Just call one of the overloaded [LoadMethod] generated in the context. So, set the event handler as before:

    void blah() {

    context.Loaded += new EventHandler<LoadedDataEventArgs>(context_Loaded);

    context.LoadSet(null, stateObject);

    }

    void context_Loaded(object sender, LoadedDataEventArgs e) {

    object state = e.UserState

    }

    As frederik said though, this does mean that end up with conditional logic. The equivalent, of course, is to use a State pattern whereby you implement a base state interface and then create specifics based on context:

    interface State {

       void HandleState(Context context);

    }

    public class LoadSetState : State {

       public void HandleState(Context context) {

       // Invoke next load

       context.LoadSet(null, new NextState());

       }

    }

    // As before

    void blah() {

    context.Loaded += new EventHandler<LoadedDataEventArgs>(context_Loaded);

    context.LoadSet(null, new LoadSetState());

    }

    void context_Loaded(object sender, LoadedDataEventArgs e) {

    State state = e.UserState as State;

    e.HandleState(context);

    }

    Its not ideal as it introduces some coupling from the State to the context, but it can remove the conditional overhead. Hope that helps.

    Tuesday, April 21, 2009 6:45 AM
  • You also have an option to create several DomainService.. it will make sure a specific "entity" will be handled by one specific DomainService. This will of course result in several classes.. but make sure each of your "domain object" will have it's own service, instead of creating one big that is responsible to return several domain objects. Remember that a class should only have one reason to change, and only one responsibility. So if you are creating one DomainService which will for example LoadCustomers and Orders, the specific DomainService will have more than one responsibilities. In some cases we maybe want to let a DomainService have several responsibilities by loading several "entites", so we can use the change tracking feature etc. But the model RIA Services uses today only uses one Loading event for all load methods.. Remeber though, if you have two query methods which will expose the same "entity", there will only be one common property to get the results for both methods.. So you can't for example separate the result into two sepereated properties.

    Tuesday, April 21, 2009 7:15 AM
  • Nedster:

    Interesting idea. However in my situation, I need to set some UI based on the entity returned, and there is a conflict between static/non-static methods and access. In other words, inside a class that implements the State Interface's HandleState method, I can't set my UI (a chart) DataContext directly (due to visibility), and I can't call a SetUI method back in the main page class because it cannot be made static (UI objects become invisible if I do). The HandleState method(s) appear to be in a static context. I implemented your approach as an Interface and instance classes both inside the Page Class and outside and got the same result. I'm pretty sure I got it right -- setting up a separate State class to process each Entity Load and then chaining on to the next. I'm also passing some datetime? parameters to the Load methods, and these had to be made public static to the page class in order to be passed to the Load methods in the HandleState method for the previous load.

    Frederik :

    I had thought about that and actually tested it, but saw no performance difference. Because 1) my app will never have thousands and thousands of rows of primary data, and 2) I take it offline with offline storage, I've decided to route all access through one public DomainService Context singleton that is instantiated on start up, so that when the user presses Save Local, all the data available can be stored locally (< 1.2 MB with a pretty healthy business base of data), and then they can work remotely until connected again and the data can be sync'd up. I did set up a separate DomainService for scalar fetches and one-way processing tasks that the user has to perform, like purge old records, update due dates, etc. There are no entities involved in that Service. I have not seen any performance hit since I implemented my architecture this way, and the magic of Silverlight binding handles an awful lot of what would otherwise be required conditional logic. The even better news is that once an entity is loaded (lookup table data for example), it is loaded for the entire session. That really speeds up the UI.

    Thanks to both of you!

    Tuesday, April 21, 2009 12:44 PM
  • I don't have time to figure out a demo today, but I did discover that there is an alternate way to send load requests through RIA Services. DomainContext implements the IDomainContext interface which exposes BeginLoad and EndLoad methods which takes a callback method that you can provide yourself. Those two methods seem to work the same way that ADO.NET Data Services works. You call BeginLoad giving it the method name (that would be the server side method name with the Get, not the Load name) and when the return comes back you give the result to EndLoad.

    Tuesday, April 21, 2009 5:09 PM
  • Along the lines of what the original post was looking for, DomainContext does expose an IsLoading property which will be true only when there are outstanding queries being executed. Perhaps that is what you were looking for with the IsBusy flag you mentioned?

    On our list of features we want to support query batching, which would allow you to specify a set of load/query operations to execute all together as a single server request. That would result in a single Loaded event when the query batch was complete. So think of something like context.Load(query1, query2, ...) where the arguments are a new type we're considering which represents a query request : something like (DomainContext.Load(params EntityQuery[] queries).

     

    Tuesday, April 21, 2009 7:52 PM
  • Query batching sounds good as long as we have that as an option, which from your method signature, looks like you would.

    Tuesday, April 21, 2009 10:23 PM
  • Thanks Mathew, IsLoading was what I was after, should have spent more time investigating the context object!

    Wednesday, April 22, 2009 4:45 AM
  • i'm not finding IsLoading very useful at all

    firstly, i put a PropertyChanged handler and a Loaded handler on my domain context

    then i make two load calls

    first handler hit is Loaded
    second handler hit is PropertyChanged on the IsLoading property with a new value of false
    third handler hit is Loaded

    so it seems, IsLoading is changed to false when a call comes back, even if other calls are still out in the wild...

    i think i will disregard IsLoading altogether and count the returns via delegates passed into the Load methods via UserState.

    is this what other people are finding?

     

    Wednesday, July 01, 2009 8:57 PM
  • Your expectation is correct - IsLoading should be true IFF there are pending ("out in the wild") requests that haven't completed. If that's not happening, that's a bug. In the upcoming July CTP, this issue will no longer be present - you'll be able to rely on IsLoading to tell the truth :)

    Also, in the July CTP we're introducing some new APIs that will facilitate query batching scenarios like these - please stay tuned.

    Thursday, July 02, 2009 12:37 PM
  • I've just upgraded to the July CTP, and I'm trying to wrap my head around the 'intended way' of facilitating query batching in the new API.   My application has a definite need for executing multiple queries simultaneously, within an 'atomic' transaction.  The code I had working against the previous RIA preview release was essentially stuffing instances of DomainContexts into a list, which was then passed to a "Manager" object whose responsibility it was to listen to the Loaded events for each DomainContext in the list and then raise it's own event when all were complete.  I am having trouble adapting a working model against the new API, where LoadOperation isn't even instantiated until the respective Load methods are called on a DomainContext.  Ideally, I'd like to be able to just pre-define a bunch of RIA calls, queue them up, and then call a Start() method or something and have them all execute at that time.  But I am failing to see an intuitive way to do that with the new LoadOperation objects.

    Is the intended paradigm shift towards the use of a single instance of a DomainContext object, calling multiple Load methods on it and then tracking it's IsLoading property via the DomainContext.PropertyChanged event handler?

    Would it be appropriate to make some use of the optional userState object in the method overload?  The Preview documentation doesn't seem to explain its purpose; I assumed it was intended for stuffing the RiaContext.Current.User in or something, but I also noticed this recent blog post: http://blog.tombeeby.com/post/2009/07/07/UserState-knowing-when-youe28099re-done-in-NET-RIA-Services.aspx

    Has anyone else been looking at this since the update?  I realize it's brand new, and still a preview, but I'd love to see some example code of a "batched RIA query" implementation, or possibly even just get a bit more verbose explanation from the RIA developers as to what is intended and now available for issuing multiple queries simultaneously, and tracking completion.
    Wednesday, July 15, 2009 12:27 PM
  • I would strongly second that request for a multiple load example (minimal UI is fine). I have not broken out my SL3 beta/RIA May CTP application yet to see everything that is broken, but this potential issue concerns me the most. I have multiple entities being loaded at startup into a common context and was checking the count of each to determine if a particular entity was loaded before doing anything else with it. Please, Dinesh?

    Wednesday, July 15, 2009 2:08 PM

  • Is the intended paradigm shift towards the use of a single instance of a DomainContext object, calling multiple Load methods on it and then tracking it's IsLoading property via the DomainContext.PropertyChanged event handler?

    I think this is a going to be a paradigm shift for you, it isn't a paradigm shift for the DomainContext. Unfortunatly, you were using the DomainContext in a way it wasn't meant to be used and the new design really doesn't work well the way you were using it. I think you could get better help if you explained your overall application needs instead of asking such a narrow question. I am not sure if that is clear so I will use an analogy, right now you are asking how to use a screwdriver to drive in a nail and I am not sure if should tell you to buy a hammer or get a screw.


    Would it be appropriate to make some use of the optional userState object in the method overload?  The Preview documentation doesn't seem to explain its purpose; I assumed it was intended for stuffing the RiaContext.Current.User in or something, but I also noticed this recent blog post: http://blog.tombeeby.com/post/2009/07/07/UserState-knowing-when-youe28099re-done-in-NET-RIA-Services.aspx

    UserState is a pretty common concept you will find in lots of async .Net code. In this case User referes to you, the programmer, not the person currenty using your program. Its original purpose is to uniquely identify an asynchronous task from when it is started to when it completes but you can pretty much stuff any object you want into it.

    Wednesday, July 15, 2009 2:09 PM
  • I would strongly second that request for a multiple load example (minimal UI is fine). I have not broken out my SL3 beta/RIA May CTP application yet to see everything that is broken, but this potential issue concerns me the most. I have multiple entities being loaded at startup into a common context and was checking the count of each to determine if a particular entity was loaded before doing anything else with it. Please, Dinesh?

    Here is one example I just came up with. Yes, this could probably be simplified with lamdas.

            private void LoadData()
            {
                List<LoadOperation> loaderList = new List<LoadOperation>();
                loaderList.Add(context.Load<Obj1>(context.GetObj1Query(),Obj1DataLoadedCallback,loaderList));
                loaderList.Add(context.Load<Obj2>(context.GetObj2Query(), Obj2DataLoadedCallback, loaderList));
                loaderList.Add(context.Load<Obj3>(context.GetObj3Query(), Obj3DataLoadedCallback, loaderList));
                loaderList.Add(context.Load<Obj4>(context.GetObj4Query(), Obj4DataLoadedCallback, loaderList));
            }
            private void Obj1DataLoadedCallback(LoadOperation<Obj1> lo)
            {
                CheckDataLoad(lo);
            }
            private void Obj2DataLoadedCallback(LoadOperation<Obj2> lo)
            {
                CheckDataLoad(lo);
            }
            private void Obj3DataLoadedCallback(LoadOperation<Obj3> lo)
            {
                CheckDataLoad(lo);
            }
            private void Obj4DataLoadedCallback(LoadOperation<Obj4> lo)
            {
                CheckDataLoad(lo);
            }
            private void CheckDataLoad(LoadOperation lo)
            {
                List<LoadOperation> loaderList = lo.UserState as List<LoadOperation>;
                ((List<LoadOperation>)lo.UserState).Remove(lo);
                if (loaderList.Count == 0)
                    LoadIsComplete();
            }
            private void LoadIsComplete()
            {
                //Do something
            }

    Wednesday, July 15, 2009 2:37 PM
  • Yes, that's the sort of "batch manager" you can build on top of LoadOperations. A colleague of mine has an internal prototype of this that he uses in his app - he's going to post it to this thread. I haven't spent much time on it, but I agree that until we add framework features around this, we should identify the "recommended pattern".

    Wednesday, July 15, 2009 2:58 PM
  • I posted a simple .NET RIA Services batch manager out on my blog this morning. With the new Load APIs in the July CTP, it was pretty easy to get a simple load batch manager up and running, and only took about 1/3 of the code compared to the implementation I had using the Mix'09 bits. If you want to see the blog post it's here: http://blogs.msdn.com/smccraw/archive/2009/07/15/a-net-ria-services-data-load-batch-manager.aspx

    and I'll repost the code here as well since it's short and sweet ;-)

    using System;
    using System.Collections.Generic;
    using System.Windows.Ria.Data;

    namespace BusinessApplication1
    {
        /// <summary>
        /// Simple LoadOperation batch manager
        /// </summary>
        /// <remarks>
        /// This class allows you to start multiple load operations in parallel and still received a single
        /// 'complete' action callback once all work is done. Load operation failures are also aggregated
        /// and will be made available as part of the completion action callback.
        /// </remarks>

        public class DomainContextLoadBatch
        {
            private List _pendingOperations = new List();
            private List _failedOperations = new List();
            private Action _completeAction;

            /// <summary>
            /// Expose the count of failed operations
            /// </summary>

            public int FailedOperationCount { get { return _failedOperations.Count; } }

            /// <summary>
            /// Expose an enumerator for all of the failed operations
            /// </summary>

            public IEnumerable FailedOperations { get { return _failedOperations; } }

            /// <summary>
            /// Public constructor
            /// </summary>
            /// <param name="completeAction"></param>

            public DomainContextLoadBatch(Action completeAction)
            {
                this._completeAction = completeAction;
            }

            /// <summary>
            /// Used to add an operation to the batch
            /// </summary>
            /// <param name="loadOperation"></param>

            public void Add(LoadOperation loadOperation)
            {
                loadOperation.Completed += new EventHandler(loadOperation_Completed);
                _pendingOperations.Add(loadOperation);
            }
           
            /// <summary>
            /// Processes each operation as it completes and checks for errors
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>

            private void loadOperation_Completed(object sender, EventArgs e)
            {
                LoadOperation loadOperation = sender as LoadOperation;
                _pendingOperations.Remove(loadOperation);
                loadOperation.Completed -= new EventHandler(loadOperation_Completed);

                if (loadOperation.Error != null)
                    _failedOperations.Add(loadOperation);

                CheckForComplete();
            }

            /// <summary>
            /// Called to check for all operations being complete and to fire our completed action
            /// </summary>

            private void CheckForComplete()
            {
                if (_pendingOperations.Count > 0)
                    return;

                if (_completeAction != null)
                {
                    _completeAction(this);
                    _completeAction = null;
                }
            }
        }
    }

     
     
    Wednesday, July 15, 2009 3:32 PM
  • One area that is much improved in the July CTP are the IsLoading & IsSubmitting properties of DomainContext

    A very simplistic way to have batching functionality (if you don't want to go as far as the batching mechanism described above) is to have a 'composite loading property' like this

     

    public bool SelectedPersonLoading
    {
        get 
        {
            return _personCtxt.IsLoading || _contactCtxt.IsLoading || _personCtxt.IsSubmitting ||  _contactCtxt.IsSubmitting; 
        }
    }

      

    and then make sure you trigger a PropertyChanged notification for SelectedPersonLoading 1. when you make the call to load or submit 2. when each load / submit returns.

    You could use this bool property then to bind to some 'async state control' like the one described by David Poll here http://www.davidpoll.com/?p=73 or a much more simplistic implementation here http://blog.tombeeby.com/post/2008/11/29/Loading-panel-control-for-asynchronous-requests.aspx

    Wednesday, July 15, 2009 7:43 PM
  •  @ColinBlair: Thanks for your input and code example.  Admittedly I don't have a wealth of experience yet with common async concepts/patterns nor with WPF-type development, so trying to build a rather sophisticated app in Silverlight 3 and RIA as a very first foray into SL has been a stretch, and I'm learning a lot as I go.  I'll try to get my toolbox organized.  ;-)

    @smccraw: This is precisely what I needed, many thanks!

    Thursday, July 16, 2009 4:28 PM