Asynchronous Queries and Coroutines

Have you ever had one of “eureka” days when the light bulb goes on above the head and the code just seems to fall into place. Well I have and it happened when I finally understood coroutines and how to use them with asynchronous queries.

One of the most difficult parts of learning Silverlight is getting used to asynchronous programming. Over they years, I have been accustomed to programming in C#, Visual Basic, c++ etc. I would issue a query to the database for some data, wait for the data to return, and then display the data on the screen for the user. With asynchronous programming, I now issue a query to the database for some data, the program continues execution and displays the screen without waiting for the return of the data. After a period of time, the data is returned and is displayed on the screen. The UI is no longer held up waiting for the data to return.

Now asynchronous programming can present some interesting challenges for the programmer. There may be a slight pause as the data populates the fields on the form. Depending on the amount of data being returned and the speed of the connection, the data may even seen to flow down the form as the fields are being filled in. Most of the problems can be avoided by retrieving the minimum amount of data needed to fill the form or if using a grid, filing the first page.

As I said before, I found asynchronous programming the most difficult part of learning Silverlight. So much so that for the longest time, I created WPF apps just so that I could use synchronous programming. It wasn’t that it was difficult to create asynchronous queries. It is quite easy to do so. Let’s look at an example of how to create an asynchronous query to retrieve inventory items.

In my repository class, I have a method called GetInventoryItems() which takes 3 parameters. The first parameter is a QueryDescriptor object, which contains the filter and sort criteria. We will examine this object in more detail in later code. The second parameter is what to do upon the successful completion of the query, and finally the last parameter indicates what to do for an unsuccessful query.


 public void GetInventoryItems(QueryDescriptor queryDescriptor, Action<IEnumerable<Inventory>> onSuccess, Action<Exception> onFail)
      {

          Manager.Inventories.Parse(queryDescriptor)
            .ExecuteAsync(
                op =>
                {
                    if (op.CompletedSuccessfully)
                    {
                        if (onSuccess != null)
                            onSuccess(op.Results);
                    }
                    else
                    {
                        if (onFail != null)
                        {
                            op.MarkErrorAsHandled();
                            onFail(op.Error);
                        }
                    }
                }
             );
      }

In my View Model class, I have a FetchInventoryItems() method that calls the repository method and passes the appropriate arguments.

      
        private void FetchInventoryItems()
        {
            this.IsBusy = true;

            _queryDescriptor = new QueryDescriptor();
            _queryDescriptor.FilterDescriptors.Add(new FilterDescriptor("Status", FilterOperator.IsEqualTo, "A"));
            _queryDescriptor.SortDescriptors.Add(new SortDescriptor("Description", ListSortDirection.Ascending));

            Repository.GetInventoryItems(_queryDescriptor,

              (items) => // query success
              {
                  this.IsBusy = false;
                  ResetInventoryItemsList(items);
              },
              (error) => // failed
              {
                  this.IsBusy = false;
                  DisplayMessage(error.Message);
              });
        }

        private void ResetInventoryItemsList(IEnumerable<Inventory> inventory)
        {
            this.InventoryItems.Clear();
            inventory.ForEach(c => InventoryItems.Add(c));
            OnPropertyChanged("InventoryItems");
        }

In the above method, I instantiate a new queryDescriptor object. I add a new FilterDescription object that filters the retrieved data to items that have an Active status and a SortDescriptor that will retrieve the data in Description order. This queryDescriptor object is passed as the first parameter to the GetInventoryItems() methods, the second argument tells the method to take the returned rows and pass them to a method called ResetInventoryItemsList which populates an ObservableCollection called InventoryItems which is bound to a UXGridView on the UXPage. If there is an error in the data retrieval process, an error message is displayed to the user.

So it was not the process of creating and using asynchronous queries that bothered me. That was easy. What bothered me was that my applications were made up of more than just simple queries. There were complicated processes that required multiple steps to be complicated with multiple queries to complete the process. Unfortunately with asynchronously processing, control returned after the query was submitted and there was no guarantee that the next process would be completed in the order or time needed.

As an example, I have an entity name Batch. It represents a deposit, a batch of payments. Its identity is a BatchNo which it obtains from a table called LocalInfo which has a field names NextBatchNo. When a batch object is created, the creation process must get the next batch number from the table, increment it by 1, write the next batch number back to the table and create the batch object. This is quite a lot to do under asynchronous processing and pretty much impossible, one would think. But thanks to IdeaBlade and Coroutines, it is quite easy to do. Coroutines allow for the batching of multiple asynchronous queries. All of the queries are batched together as a group. I am going to show a practical example of using Coroutines in a line of business application.

From the Batch ListView form, the user clicks on the “Add New Batch” button. The button is bound to a AddCommand in the View Model class. Here is the AddBatch() method.


      private void AddBatch(object parameter)
        {
            this.IsInEditing = true;

            BatchEditor.AddNewBatch(
              AddedBatchSaved, // Saved callback
              EditorClosed); // Cancelled callback
        }

All data entry is done in a sandbox editor. The job of the sand box editor is to create the dialog box for data entry, associate it with a view model and reports back to the calling view model whether the Save/OK button or cancel button was clicked.

In the BatchEditSandboxEditor class, I have an AddNewBatch() method which will call the create method of the batch and then show the dialog box for data entry.


       public override void AddNewBatch(Action<Batch> onOk, Action onCancel)
        {
            Repository.ClearAllBatches();

            var coop = Coroutine.Start(Repository.CreateBatch);
            coop.Completed += (sender, args) =>
                {
                    if (args.CompletedSuccessfully)
                    {
                        var batch = coop.Result as Batch;
                        ShowBatchDialogBox(batch, onOk, onCancel);

                    }
                };
        }

In the AddNewBatch() method, I first clear the cache of any existing batches in memory. I then start a new Coroutine method which will wrap the CreateBatch() method in the repository class. After the batch is completely finished with all of its batch processing, the AddNewBatch() method instantiates a batch object from the returned results and then displays the dialog box.

In the repository class is the coroutine method CreateBatch().


        public IEnumerable<INotifyCompleted> CreateBatch()
        {

            var NextBatchOperation = Manager.LocalInfos
                .ExecuteAsync();

            yield return NextBatchOperation;

            LocalInfo li = NextBatchOperation.Results.First();

            var batchNo = li.NextBatchNo;

            Int32? nextBatchNo = batchNo + 1;

            li.NextBatchNo = nextBatchNo;

            var SaveNextBatchOperation = Manager.SaveChangesAsync(new[] {li});
            SaveNextBatchOperation.Completed += (sender, args) =>
                {
                    var savedEntities = args.Entities;
                    var sr = args.SaveResult;
                    var error = args.Exception;
                };

            yield return SaveNextBatchOperation;
            
            var batch = new Batch()
                {
                    BatchNo = Int32.Parse(batchNo.ToString()),
                    BatchDate = DateTime.Now,
                    UserCd = CommonUser.CurrentUser.UserCd,
                    Office = CommonUser.CurrentUser.Office,
                    Status = "O"
                };
                Manager.AddEntity(batch);

            yield return Coroutine.Return(batch);
        }

A Coroutine method returns a IEnumerable result. A Coroutine method wraps up multiple asynchronus statements. After each asynchronous statement a yield return statement is inserted into the code. The yield return statements returns the query operation from the asynchronous statement. At the end of the method, yield return Coroutine.Return statements returns the results of the method back to the method holding the Coroutine.Start() method. The statements within the Coroutine method are executed as a batch and the method returns only after all the statements are executed.

Here is the dialog box after the batch is created and the sand box instantiates and displays it.

The batch number, has been retrieved from the LocalInfo table, the next batch number is incremented by 1, and and been saved back the LocalInfo table, ready for the next batch to be created.

I hope that this blog about using Coroutines to batch asynchronous queries together has been helpful and will serve as an example for your own applications.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s