Up Start
DevForce 2010 Resource Center » DevForce development » Start » Program asynchronously

Program asynchronously

Last modified on August 15, 2012 17:22

All client / server communications in Silverlight must be asynchronous and desktop applications probably should be. Asynchronous programming requires some care and techniques that may be unfamiliar. This topic describes the asynchronous operations in DevForce and how to use them.


The DevForce EntityManager in the full .NET library sports numerous methods for communicating from the client to the server, split 50/50 between synchronous and asynchronous versions.

DevForce synchronous operations

When using the synchronous versions, the client code makes a call and waits for the server to return.

C#
results = manager.ExecuteQuery(someQuery);
doSomething(results); // called after server returns
VB
results = manager.ExecuteQuery(someQuery)()
doSomething(results) ' called after server returns

The application on the calling thread blocks until the server returns its reply. If it takes awhile, the calling code waits. If the calling code is UI code, the user will be staring at a frozen screen until the queried entities arrive. 

It could be a long wait. The asynchronous operations might be a better choice. They don't deliver server results any faster. They do let you keep the UI alive. The user might be able to do other work while waiting. Even if they can't, the application can show that it is still running and perhaps tell the user how matters are progressing.

DevForce asynchronous operations

Many synchronous methods aren't even available in the Silverlight EntityManager, since Silverlight disallows synchronous communications with the server.  Only queries which may be fulfilled from cache can be executed synchronously in Silverlight.  All service methods are asynchronous; they include:

You have to take a different approach when using these methods. The synchronous code we wrote earlier doesn't translate literally to an asynchronous alternative.  For example, this won't do what you're expecting:

C#
// Not what you are expecting
results = manager.ExecuteQueryAsync(someQuery);
doSomething(results); // called immediately, before results arrive
VB
' Not what you are expecting
results = manager.ExecuteQueryAsync(someQuery)()
doSomething(results) ' called immediately, before results arrive

Two critical observations:

  • The results variable is not an entity collection. Asynchronous methods don't return results. They return an operation coordinator object, as we'll see.
  • That "operation coordinator" object will contain queried entities at some point in the future.  But query results won't be available when doSomething(results) is called.
More precisely, they won't be available if query processing includes a trip to the server. If the query can be satisfied entirely from cache, the results are returned immediately and the operation appears to be synchronous. We ignore that possibility for now as this discussion concerns operations that talk to the server.

It is in the nature of any asynchronous method call that control flows immediately to the next line and you cannot predict when the server will return with results. If you care when the results arrive, you must provide some means for the asynchronous operation to notify you.

DevForce asynchronous methods can notify you in one of two ways:

  1. by invoking your callback method
  2. by raising a Completed event
C#
// with callback
manager.ExecuteQueryAsync(query1, queryCompletedCallback);
// with CompletedEvent
operation = manager.ExecuteQueryAsync(query2);
operation.Completed += queryCompletedHandler;
VB
' with callback
manager.ExecuteQueryAsync(query1, queryCompletedCallback)
' with CompletedEvent
operation = manager.ExecuteQueryAsync(query2)
AddHandler operation.Completed, AddressOf queryCompletedHandler

All DevForce asynchronous methods support these two approaches. 

Anatomy of an asynchronous method

The ExecuteQueryAsync signature tells a more complete story:

C#
public EntityQueryOperation<T> ExecuteQueryAsync<T>(
    IEntityQuery<T> query,
    Action<EntityQueryOperation<T>> userCallback = null,
    Object userState = null)
VB
Public Function ExecuteQueryAsync(Of T)(
   ByVal query As IEntityQuery(Of T), _
   ByVal Optional userCallback As Action(Of EntityQueryOperation(Of T)) = Nothing, _
   ByVal Optional userState As Object = Nothing _
   As EntityQueryOperation(Of T)

Let's break this signature down starting with the method's returned value and proceeding through the parameters.

All asynchronous methods return an "operation coordinator" object ending in the word "Operation". In this example, it is an EntityQueryOperation with a type parameter 'T' specifying the type of the query result. 'T' would be Customer if the query returned Customer entities. 

The other asynchronous methods have their own operation object types such as the EntitySaveOperation for a save. They all derive from the DevForce BaseOperation  class and therefore share many of the same behaviors and members such as a Completed event.

The operation object is not the operation itself. It is an object that represents the operation in progress. You can ask it questions such as "are you finished?" and "do you have errors?". You can tell it to cancel the operation. It is a vehicle for coordinating the caller and the operation. Most developers just call it "operation" for the sake of brevity.

Most asynchronous methods take a functional parameter such as the query. This is the same parameter that would be passed into the corresponding synchronous method.

Next is the optional callback method. The callback is an Action that takes the operation coordination object as its sole parameter. This is the same object that was returned by the method, the EntityQueryOperation<T> object in our example.

The last parameter is an optional userState. The userState is an arbitrary object of your choosing. You supply a userState when you need to pass some information to the callback method or Completed event handler. You might use it to distinguish one query from another. The userState object can be as simple as an integer or it can be an arbitrarily complex custom type. DevForce assigns a Guid if you don't specify a value.

The userState object need not be serializable because it never leaves the client; the server never sees it.

Callback methods

An asynchronous operation callback method takes an operation coordination object parameter as seen in this example.

C#
void AllCustomersQuery() {
    var query = new EntityQuery<Customer>();
    manager.ExecuteQueryAsync(query, CustomerQueryCallback);
}

void CustomerQueryCallback(EntityQueryOperation<Customer> op) {
   if (op.CompletedSuccessfully) {
        var resultList = op.Results;
        Console.WriteLine("Query returned {0} entities", resultList.Count());
    } else { /* do something with op.Error */ }
}
VB
Sub AllCustomersQuery()
   Dim query = New EntityQuery(Of Customer)
    manager.ExecuteQueryAsync(query, AddressOf CustomerQueryCallback)
End Sub

Sub CustomerQueryCallback(ByVal op As EntityQueryOperation(Of Customer))
   If (op.CompletedSuccessfully) Then
      Dim resultList = op.Results
       Console.WriteLine("Query returned {0} entities", resultList.Count())
   Else
      ' do something with op.Error
   End If    
End Sub
Catching errors and handling them properly is always important, especially for communications with a server.  The server or network may go down at any time, so all server calls should have error handling to ensure your application doesn't crash.  You can't wrap your asynchronous calls  in a try/catch; you must handle errors explicitly via the operation object, a subject covered below.
Do not call the Results property unless the operation has completed successfully. It is a mistake to ask for results when they are undefined. DevForce can't simply return null because null is a potentially valid query result. DevForce throws an exception instead.

Many developers prefer to use a lambda callback for economy of expression:

C#
void AllCustomersQuery() {
    var query = new EntityQuery<Customer>();
    manager.ExecuteQueryAsync(query, op => {
       if (op.CompletedSuccessfully) {
            var resultList = op.Results;
            Console.WriteLine("Query returned {0} entities", resultList.Count());
        } else { /* do something with op.Error */ }
    });
}
VB
Sub AllCustomersQuery()
   Dim query = New EntityQuery(Of Customer)
    manager.ExecuteQueryAsync(query, Sub(op)
       If (op.CompletedSuccessfully) Then
          Dim resultList = op.Results
           Console.WriteLine("Query returned {0} entities", resultList.Count())
       Else
          ' do something with op.Error
       End If    
   End Sub)
End Sub

Completed event handlers

Instead of a callback you may prefer to listen to the Completed event. In the next code sample, we simplify the query and call its ExecuteAsync method instead of the EntityManager's ExecuteQueryAsync. The principles and practices are the same as before.

C#
void AllCustomersQuery() {
    var op = manager.Customers.ExecuteAsync();
    op.Completed += CustomerQueryCompletedHandler;
}

void CustomerQueryCompletedHandler(
   object sender, EntityQueriedEventArgs<Customer> args) {
   if (args.CompletedSuccessfully) {
        var resultList = args.Results;
        Console.WriteLine("Query returned {0} entities", resultList.Count());
    } else { /* do something with args.Error */ }
}
VB
Sub AllCustomersQuery()
   Dim op = manager.Customers.ExecuteAsync()
   AddHandler op.Completed, AddressOf CustomerQueryCompletedHandler
End Sub

Sub CustomerQueryCompletedHandler( _
   ByVal sender as Object, ByVal args As EntityQueryOperation(Of Customer))
   If (args .CompletedSuccessfully) Then
      Dim resultList = args .Results
       Console.WriteLine("Query returned {0} entities", resultList.Count())
   Else
      ' do something with args .Error
   End If    
End Sub

The handler takes an EventArgs instead of an operation coordination object but the effect is the same.

You can use a lambda expression here too:

C#
void AllCustomersQuery() {
  var op = manager.Customers.ExecuteAsync();
  op.Completed += (o, args) => {
   if (args.CompletedSuccessfully) {
      var resultList = args.Results;
      Console.WriteLine("Query returned {0} entities", resultList.Count());
    } else { /* do something with args.Error */ }
  };
}
VB
Sub AllCustomersQuery()
 Dim op = Manager.Customers.ExecuteAsync()
 AddHandler op.Completed,
    Sub(o As Object, args As EntityQueriedEventArgs(Of Customer))
     If args.CompletedSuccessfully Then
       Dim resultList = args.Results
        Console.WriteLine("Query returned {0} entities", resultList.Count())
     Else ' do something with args.Error
     End If
   End Sub
End Sub

The operation coordination object

The operation coordination object and the EventArgs have additional members. These differ depending upon the operation performed; consult the API documentation for details. BaseOperation  reveals the members in common.

MemberDescription
CanCancelGet if this operation is cancellable.
Cancel()Try to cancel the operation. It may (still) be cancellable.
CancelledGet if the operation was canceled before it completed.
Completed Raised when the operation has completed, successfully or otherwise. A synonym for IsCompleted.
CompletedSuccessfully Get if the operation completed without error and without having been cancelled.
CompletedSynchronously Get if the operation was actually processed synchronously entirely on the caller's thread without a trip to the server. This happens frequently with query operations that are completely satisfied by cached entities.
Error If the operation failed, this property returns the error as an Exception. Returns null if there is no error (yet).
HasError Get if the operation failed with an error.
IsCompleted Get if the operation finished, successfully or otherwise. A synonym for Completed.
IsErrorHandled Get or set whether the error was handled. Error handling logic must set this to true; you usually call the MarkErrorAsHandled method. If the operation failed and this property is not true, DevForce re-throws the error, an exception that you cannot catch is likely to terminate the application. See error handling for details.
MarkErrorAsHandled()Sets the IsErrorHandled property to true.
PropertyChanged Raised when one of the operation's public properties has changed.
UserState Get the custom object that carries information from the caller to the callback.

Error handling

We mentioned above that you can't wrap your asynchronous method in a try/catch.  But you must still be on the lookout for errors, since any unhandled exception from an asynchronous operation will terminate the application.

The exception raised by the asynchronous operation is returned in the Error property of the operation or EventArgs object.  These expose the following properties useful in error detection and handling:

It's your responsibility to inspect the operation object when the async operation completes and address failures.

Mark the error "as handled"

If an async operation completes with an error and you don't either call MarkErrorAsHandled or set IsErrorHandled to true, then DevForce will re-throw the exception.  You won't be able to catch this re-thrown exception, and your application will crash. 

If you don't want the unhandled exception to terminate your application, you must:

  1. Detect the exception by examining the async operation HasError property.
  2. Process the exception as you see fit.  The only viable option may be to log the exception or display a user-friendly error message.  If a communication failure has occurred you can switch your application to offline mode.  How you handle the error is up to you.
  3. If the application should continue then mark the error as handled.  Remember that users don't generally appreciate an application which crashes with obscure error messages.

Here's our query completed handler from earlier with a bit more error handling:

C#
void CustomerQueryCompletedHandler(object sender, EntityQueriedEventArgs<Customer> args)
 if (args.CompletedSuccessfully) {
    var resultList = args.Results;
    Console.WriteLine("Query returned {0} entities", resultList.Count());
  } else if (args.HasError) {
    HandleError(args.Error);    // Your choice - log the error, show a message, ...
   args.MarkErrorAsHandled();
  }
}
VB
Sub CustomerQueryCompletedHandler( _
   ByVal sender as Object, ByVal args As EntityQueryOperation(Of Customer))
 If args.CompletedSuccessfully Then
    Dim resultList = args.Results
     Console.WriteLine("Query returned {0} entities", resultList.Count())
  ElseIf args.HasError Then
     HandleError(args.Error)   ' Your choice - log the error, show a message, ...
    args.MarkErrorAsHandled()
  End If
End Sub

IAsyncResult asynchronous pattern

The EntityManager also supports the IAsyncResult asynchronous pattern through an explicit implementation of the IEntityManagerAsync interface . You will need to cast an EntityManager to this interface in order to use methods that follow this pattern. 

In the IAsyncResult pattern, an asynchronous operation named "SomeOperation" is implemented as two methods named BeginSomeOperation and EndSomeOperation.

Tags: Asynchronous
Created by DevForce on November 29, 2010 13:39

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2015 IdeaBlade