Up Core concepts
DevForce Resource Center » Core concepts » Program asynchronously

Program asynchronously

Last modified on February 21, 2013 17:46

DevForce supports both synchronous and asynchronous client/server communications.  Some environments, such as Silverlight, Windows Store and mobile applications, allow only asynchronous communication, but asynchronous communications are also recommended in all environments for any potentially time-consuming operation which might block the application UI.  Asynchronous programming requires some care and techniques that may be unfamiliar. This topic describes the asynchronous operations in DevForce and how to use them.


See Visual Studio Asynchronous Programming for more information on the new async features in Visual Studio 2012.

Unknown macro: IBNote

DevForce synchronous operations

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.

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

C#
var query = manager.Customers.Where(c=> c.Country == "UK");
results = manager.ExecuteQuery(query);
doSomething(results); // called after server returns
VB
Dim query = From c In manager.Customers Where c.Country = "UK"
Dim results = manager.ExecuteQuery(query)
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

DevForce provides asynchronous methods for all operations which communicate with the EntityServer.

On the EntityManager:

On the Authenticator:

All async methods return a Task or Task<TResult>.  The task represents the asynchronous operation, and will indicate the status of the operation, the results of a completed operation, and whether the operation was cancelled or failed.

Note that the task returned from a DevForce async method is "hot": it has already started and is scheduled for execution.  

You will generally await (or Await in Visual Basic) a task, but you can also wait on one or more tasks, or add conditional continuation logic.  For more information see Asynchronous Programming with Async and Await.

Anatomy of an asynchronous method

Let's take the synchronous ExecuteQuery we saw earlier and make it async:

C#
public async void SomeMethod() {
  var query = manager.Customers.Where(c=> c.Country == "UK");
  var results = await manager.ExecuteQueryAsync(query);
  doSomething(results);
}
VB
Public Async Sub SomeMethod()
   Dim query = From c In manager.Customers Where c.Country = "UK"
   Dim results = Await manager.ExecuteQueryAsync(query)
    doSomething(results)
End Sub

It's that simple.  We added the async (or Async in Visual Basic) modifier to indicate that the method contains asynchronous code, and the await (or Await) keyword to indicate that further processing in the method should be suspended until the asynchronous task completes.  In the snippet above, the doSomething method will be called when the asynchronous query completes.

Cancelling an asynchronous operation

The asynchronous DevForce operations which may be cancelled include a CancellationToken parameter in their argument list.  You'll pass the CancellationToken when calling the method, and if at some point cancellation is requested DevForce will note this request and attempt to cancel the task.

Here's a simple example in which a ConnectAsync call will be cancelled if it does not complete within 2 seconds:

C#
public async void TryConnect() {
  var manager = new DomainModelEntityManager(false);
  var cts = new CancellationTokenSource();
  cts.CancelAfter(2000);
 try {
    await manager.ConnectAsync(cts.Token);
  } catch (OperationCanceledException oce) {
    MessageBox.Show("The connection attempt was cancelled after 2 seconds.");
  } catch (EntityServerConnectionException esce) {
    MessageBox.Show("The connection attempt failed.");
  }
}
VB
Public Async Sub TryConnect()
   Dim manager As New DomainModelEntityManager(False)
   Dim cts As New CancellationTokenSource()
    cts.CancelAfter(2000)
   Try
        Await manager.ConnectAsync(cts.Token)
   Catch oce As OperationCanceledException
        MessageBox.Show("The connection attempt was cancelled after 2 seconds.")
   Catch esce As EntityServerConnectionException
        MessageBox.Show("The connection attempt failed.")
   End Try
End Sub

Note that not all asynchronous DevForce operations can be cancelled.  Also realize that a cancellation request is just that, a request, and depending on the processing state of the async operation, may not be honored.

Error handling

An awaited task will throw an exception if the asynchronous operation fails.  It will also raise an OperationCanceledException if the operation was cancelled.  To catch these exceptions, await the task within a try/catch block:

C#
public async void SomeMethod() {
  var query = manager.Customers.Where(c => c.Country == "UK");
 try {
    var results = await manager.ExecuteQueryAsync(query);
    doSomething(results);
  } catch (OperationCanceledException oce) {
    MessageBox.Show("Operation cancelled");
  } catch (EntityServerException ese) {
    MessageBox.Show(ese.Message);
  }
VB
Public Async Sub SomeMethod()
   Dim manager As New DomainModelEntityManager()
   Dim query = From c In manager.Customers Where c.Country = "UK"
   Try
       Dim results = Await manager.ExecuteQueryAsync(query)
        doSomething(results)
   Catch oce As OperationCanceledException
        MessageBox.Show("Operation cancelled")
   Catch ese As CustomEntityServerException
        MessageBox.Show(ese.Message)
   End Try

End Sub

Remember that you can also wrap a try/catch block around multiple asynchronous operations which can save a lot of redundant code. Here is another example:

C#
 public class AsyncErrorHandling {
   private readonly NorthwindIBEntities _em;

   public AsyncErrorHandling() {
      _em = new NorthwindIBEntities();    
    }

   private async Task<Customer> GetCustomer() {
     return await _em.Customers.AsScalarAsync().FirstOrDefault();
    }

   private async Task<IEnumerable<Order>> GetOrders(Guid customerId) {
     return await _em.Orders.Where(x => x.CustomerID == customerId).ExecuteAsync();
    }

   private async void Load() {
     try {
        var customer = await GetCustomer();

       if (customer != null) {
          var orders = await GetOrders(customer.CustomerID);
        }
      } catch (TaskCanceledException) {
       // Do something when any of the queries get cancelled.
     } catch (Exception) {
       // Handle error if any of the queries above fail
     }
    }
  }

Combining async tasks

If you're familiar with DevForce 2010 you might have used Coroutines to batch your async operations:  a "serial" coroutine to process a sequence of async tasks one after another; and a "parallel" coroutine to process a number of async tasks at one time.  Task-based asynchronous programming removes the need for the Coroutine, as it's now much simpler to write async code.

"Serial" async operations are now easy, and can be coded similar to their synchronous counterparts.  For example, if you wish to query a customer, make some changes, and then save those changes, this can all be done in a familar coding style:

C#
public async void UpdateCustomer() {
  var customer = await manager.Customers.AsScalarAsync().FirstOrDefault(c => c.Id == 1);
  customer.Phone = "123-456-7890";
  await manager.SaveChangesAsync();
}
VB
Public Async Sub UpdateCustomer()
   Dim aCustomer = Await manager.Customers.AsScalarAsync().FirstOrDefault(Function(c) c.Id = 1)
    aCustomer.Phone = "123-456-7890"
    Await manager.SaveChangesAsync()
End Sub

What if you'd like to work with the Task directly, or chain tasks together.  The ContinueWith method gives you many options:

C#
public async void UpdateCustomer() {

  await manager.Customers.AsScalarAsync().FirstOrDefault(c => c.Id == 1)
    .ContinueWith(t => t.Result.Phone = "123-456-7890", TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously)
    .ContinueWith(t => manager.SaveChangesAsync(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);
}
VB
Public Async Sub UpdateCustomer2()

    Await manager.Customers.AsScalarAsync().FirstOrDefault(Function(c) c.Id = 1) _
        .ContinueWith(Sub(t) t.Result.Phone = "123-456-7890", TaskContinuationOptions.OnlyOnRanToCompletion Or TaskContinuationOptions.ExecuteSynchronously) _
        .ContinueWith(Sub(t) manager.SaveChangesAsync(), TaskContinuationOptions.OnlyOnRanToCompletion Or TaskContinuationOptions.ExecuteSynchronously)
End Sub

To run a number of tasks in parallel, just fire them off.  The WhenAll method is analogous to the parallel coroutine, and will suspend execution until all the waited upon tasks have completed.

C#
public async void ParallelQueries() {
  var manager = new DomainModelEntityManager();
  var task1 = manager.Products.ExecuteAsync();
  var task2 = manager.Categories.ExecuteAsync();
  await Task.WhenAll(task1, task2);
}
VB
Public Async Sub ParallelQueries()
   Dim manager As New DomainModelEntityManager()
   Dim task1 = manager.Products.ExecuteAsync()
   Dim task2 = manager.Categories.ExecuteAsync()
    Await Task.WhenAll(task1, task2)
End Sub


These simple examples should give you a taste of the power of the new Task-based asynchronous API.


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 © 2020 IdeaBlade