Introduction
Asynchronous communication is becoming more and more popular and widespread nowadays. It is mainly used to improve performance in multi-processor environments and to increase responsiveness of applications. At the latest with the introduction of Silverlight, asynchronous communication has become a must, because all web requests in that runtime happen asynchronously. This trend continues with Windows 8 on an even more fundamental level, as many system calls happen asynchronously there.
Implementing asynchronous communication asks for additional skills in developers, because they lose control over the sequence of operations to a certain degree, which in turn leads to distributed but still related pieces of source code, which contradicts some OO paradigms.
This article describes some simple means by which dealing with asynchronous communication can be greatly simplified. The associated library provides several tools with the following benefits:
- Unification of the sequence of operations in 4 areas: Main, OnSuccess, OnError, OnFinally
- Combination of the code of a sequence of operation
- Type safety of return values and parameters through usage of generics
- Automatic reuse of functions' return values
- Simplification of error and exception handling
- Centralizing the core of asynchronous communication in one place for easier analysis, logging etc.
Asynchronous Action
To asynchronously execute an action, the library provides the class AsyncAction
, which in turn offers various utility methods for starting it. The most elaborate signature is defined as follows:
public static void Start(
Invocable main,
Invocable onSuccess,
InvocableAction1Base<Exception> onError,
Invocable onFinally,
AsyncContext context )
The parameters main
, onSuccess
, onError
, onFinally
represent the code blocks according to the above operation sequence diagram. In case Main is executed without error, the OnSuccess block is started. Otherwise the resulting Exception
is given as a parameter to OnError. For the use of AsyncAction
, a given Main action is mandatory. The AsyncContext
encapsulates the execution context and is described further down.
In the following example, an OnSuccess action is used aside from the Main action:
public void DeleteAllCustomersMinimal()
{
AsyncAction.Start(
new InvocableAction0( Services.DeleteAllCustomers ),
new InvocableAction0( () => Customers.Clear() ) );
}
Actions are available in various variants with up to 4 parameters: InvocableAction0
– InvocableAction4
:
Given this inheritance hierarchy, calls to the respective actions can be customized individually as required. The following example demonstrates how to furnish the OnError action with an additional Customer
parameter by using the InvocableAction2
:
public void DeleteCustomer( Customer customer )
{
AsyncAction.Start(
new InvocableAction1<int>(
Services.DeleteCustomer, customer.Id ),
new InvocableAction1<Customer>(
c => Customers.Remove( c ), customer ),
new InvocableAction2<Exception, Customer>(
( e, c ) => ShowError( e + " - " + c ), customer ),
new InvocableAction1<Customer>(
c => Debug.WriteLine( "DeleteCustomer: " + c ) ) );
}
Asynchronous Function
Asynchronous functions can be started through the different variants of the start method in the class AsyncFunction
. The most elaborate signature with all execution parameters is defined as follows:
public static void Start(
InvocableFunction<TResult> main,
InvocableAction1Base<TResult> onSuccess,
InvocableAction1Base<Exception> onError,
Invocable onFinally,
AsyncContext context )
The parameters main
, onSuccess
, onError
, onFinally
represent the code blocks according to the above operation sequence diagram. Contrary to the AsyncAction
, the parameter Main is of type InvocableFunction
, hence returning a result. In case Main is executed without error, the OnSuccess block is started. The return value of the Main function is given to the OnSuccess action as its first parameter. In case execution of the Main function results in an exception, it is passed as a parameter to the OnError action. Otherwise the resulting Exception
is given as a parameter to OnError. For the use of AsyncFunction
, a given Main function and a corresponding OnSuccess action are mandatory. The AsyncContext
encapsulates the execution context and is described further down.
The following example demonstrates how the result of the Main Function is passed to the OnSuccess action:
public void GetCustomerSalesVolumeMinimal( Customer customer )
{
AsyncFunction<double>.Start(
new InvocableFunction1<int, double>(
Services.GetCustomerSalesVolume, customer.Id ),
new InvocableAction2<double, Customer>(
( sv, c ) => c.SalesVolume = sv, customer ) );
}
Suitable arguments for the Main parameter of AsyncFunction
can be functions with up to 4 parameters InvocableFunction0
– InvocableFunction4
:
Execution context
At the center of asynchronous communication is the class AsyncContext
, which is used by AsyncAction
and AsyncFunction
. It governs the following aspects:
Dispatcher |
The Dispatcher which is used to actually run the asynchronous Action or Function. By default it will use the Dispatcher of the current thread. |
Thread |
Given the property ShouldInvokeOnDispatcherThread , it is possible to decide whether the Dispatcher should be run in the current Thread or via a ThreadPool in separate Thread. |
Invoke |
Implements the central operation sequence which actually executes the asynchronous action or function. |
Error handling
For proper handling of server side errors in case of asynchronous web communication (Silverlight), further steps are required. It is necessary to encapsulate errors which occur on the server and transfer them (via WCF) to the client.
This article abstains from a description of fault handling because it is not strictly on its main topic and beyond its scope. There are however many examples of how to accomplish this, for example in Catch and Handle WCF Service Exceptions in Silverlight.
Examples of application
The bundled sample application for WPF, Silverlight and Windows Phone demonstrates how to load a list of customer information items asynchronously:
private void LoadCustomers()
{
AsyncFunction<CustomerCollection>.Start(
new InvocableFunction0<CustomerCollection>( services.LoadCustomers ),
new InvocableAction1<CustomerCollection>( OnCustomersLoaded ),
new InvocableAction1<Exception>( OnCustomerLoadError ),
new InvocableAction1<string>( OnFinished, "LoadCustomers" ) );
}
private void OnCustomersLoaded( CustomerCollection customers )
{
CustomerList.ItemsSource = customers;
if ( customers != null && customers.Count > 0 )
{
CustomerList.SelectedIndex = 0;
}
AddToHistory( string.Format( "{0} customers loaded", customers != null ? customers.Count : 0 ) );
}
private void OnCustomerLoadError( Exception exception )
{
AddToHistory( string.Format( "error: {0}", exception.Message ) );
}
private void OnFinished( string action )
{
AddToHistory( string.Format( "'{0}' finished", action ) );
}
After the customers have been loaded, they are displayed in a list. Selecting a customer in that list will show its data in a detail section. Additionally, the sales volume of that customer is fetched asynchronously from an external service:
private void UpdateCustomerInfo( Customer customer )
{
CustomerId.Text = customer.Id.ToString( CultureInfo.InvariantCulture );
CustomerFirstName.Text = customer.FirstName;
CustomerLastName.Text = customer.LastName;
AsyncFunction<double>.Start(
new InvocableFunction1<int, double>( services.GetCustomerSalesVolume, customer.Id ),
new InvocableAction2<double, Customer>(
( salesVolume, cust ) =>
{
cust.SalesVolume = salesVolume;
CustomerSalesVoume.Text = salesVolume.ToString( "C" );
AddToHistory( string.Format( "customer {0} sales volume: {1:C}", cust, salesVolume ) );
},
customer ),
new InvocableAction2<Exception, Customer>(
( e, cust ) =>
{
CustomerSalesVoume.Text = string.Empty;
AddToHistory( string.Format( "error get sales volume: {0}", cust ) );
},
customer ),
new InvocableAction1<string>(
action => AddToHistory( string.Format( "'{0}' finished", action ) ),
"GetCustomerSalesVolume" ) );
}
Acknowledgement
Special thanks to Leon Poyyayil for the design and initial implementation and his support and contribution in the development of this component.
History
- 1st June, 2012 - v1.0.0.0