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.
The "IBNote" macro is not in the list of registered macros. Verify the spelling or contact your administrator.
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 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.
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.
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.
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 } } } |
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.