The upcoming Visual Studio 2012 and C# 5.0 will bring a really cool feature – the async/await magic keyword. Utilizing the power of the System.Threading.Tasks.Task, they will make it much easier to create asynchronous applications. And this will lead us to the new shiny world of responsive and robust applications. I hope.
Both RemObjects SDK and Data Abstract provide asynchronous interfaces. Yet they are based on the old Begin/End asynchronous pattern. Although still valid, this pattern feels outdated and results in having to write more code. Just imagine – all the callbacks, the delegates for handling method call results, the UI thread marshalling issues and so on, and so on. Wouldn’t it be cool to be able to call Data Abstract servers using code like this:
dgClients.ItemsSource= await LoadDataAsync(linqRemoteDataAdapter.GetTable<Clients >().Select(x => x)); |
privatevoid selectClients(){var lQuery =(from x in linqRemoteDataAdapter.GetTable< Clients>()select x); IQueryable [] lQueries =new IQueryable []{ lQuery }; linqRemoteDataAdapter.BeginExecute( lQueries, delegate( IAsyncResult ar){ linqRemoteDataAdapter.EndExecute(ar); Dispatcher.BeginInvoke(new ProcessSelectClientsResultCb (ProcessSelectClientsResult), lQuery.ToList<Clients >());}, null);} publicdelegatevoid ProcessSelectClientsResultCb (List <Clients > result); publicvoid ProcessSelectClientsResult( List< Clients> result){this.dgClients.ItemsSource= result;} |
Please note that in this application I won’t use the MVVM approach, because I want to put the focus on the asynchronous service calls and to simplify the code used in the article.
Begin/End-based Application
Let’s create a Silverlight application that will access an MS SQL database containing this data table:
CREATETABLE[dbo].[Orders]([OrderId][INT]IDENTITY(1,1)NOTNULL,[OrderDetails][nvarchar](50)NOTNULL,CONSTRAINT[PK_Orders]PRIMARYKEY([OrderId]ASC)) |
Change the MainPage.xaml layout file to:
Now let’s define actions for the buttons added to the MainPage. This is the full code of the MainPage.xaml.cs file:
usingSystem;usingSystem.Linq;usingSystem.Windows;usingSystem.Windows.Controls;usingRemObjects.DataAbstract.Linq;usingSilverlightApplication.SilverlightApplicationDataset; namespace SilverlightApplication {publicpartialclass MainPage: UserControl {#region Private fieldsprivate DataModule fDataModule;private DABindingList<Orders> fOrdersList;#endregion public MainPage(){this.InitializeComponent(); this.fDataModule=new DataModule ();} privatevoid LogOn(){this.fDataModule.LogOn("a" , "a" , null);} privatevoid MainPage_Loaded(object sender, RoutedEventArgs e){this.LogOn();} privatevoid LoadData(object sender, RoutedEventArgs e){var lQuery =(from o inthis.fDataModule.DataAdapter.GetTable<Orders >()select o); IQueryable [] lQueries =new IQueryable []{ lQuery }; this.fDataModule.DataAdapter.BeginExecute( lQueries, (ar)=>{this.fDataModule.DataAdapter.EndExecute(ar); this.fOrdersList= lQuery.ToDABindingList(); Dispatcher.BeginInvoke(()=>{this.dataList.ItemsSource=this.fOrdersList;});}, null); } privatevoid InsertData(object sender, RoutedEventArgs e){var lDummyData =new Orders(); lDummyData.OrderDetails="Object created at "+ DateTime .UtcNow.ToString(); this.fOrdersList.Add(lDummyData);} privatevoid ApplyChanges(object sender, RoutedEventArgs e){this.fDataModule.DataAdapter.BeginApplyChanges((ar)=>{this.fDataModule.DataAdapter.EndApplyChanges(ar);}, null);}}} |
When data changes are applied on the server and the underlying data table contains auto inc or any other server-generated or server-calculated fields, Data Abstract sends the values of these fields back to the client where the source data object is updated with the refreshed values. In the code above, this update happens when the EndApplyChanges method is called. And this results in an exception, because the EndApplyChanges method updates one of the objects contained in the fOrdersList entities list. But this method is executed in a thread other than the main UI thread, while the fOrdersList is bound to the UI property this.dataList.ItemsSource. So the entire application fails with an “Invalid cross-thread access.” exception.
To avoid cross-thread access, the ApplyChanges code is even more of a burden:
privatevoid ApplyChanges(object sender, RoutedEventArgs e){this.fDataModule.DataAdapter.BeginApplyChanges((ar)=>{Dispatcher.BeginInvoke(()=>{this.fDataModule.DataAdapter.EndApplyChanges(ar);});}, null);} |
Now let’s move to the second part of this article and try to use the opportunities given by the new async/await features.
Async/Await-based Application
Surprisingly, Silverlight 5 in the Visual Studio 2012 RC doesn’t recognize the async/await keywords out of the box. You have to install the NuGet package Microsoft.CompilerServices.AsyncTargetingPack to be able to use this language feature. Thankfully, this operation is very straightforward:
- Right-click the Silverlight project in the Solution Explorer and select the “Manage NuGet Packages…” menu item.
- The NuGet main dialog will be shown. Search for the package Microsoft.CompilerServices.AsyncTargetingPack and install it.
privatevoid ApplyChanges(object sender, RoutedEventArgs e){this.ApplyChangesAsync();} async Task ApplyChangesAsync(){ await Task .Factory.FromAsync(this.fDataModule.DataAdapter.BeginApplyChanges, (ar)=>{ Dispatcher.BeginInvoke(()=>{this.fDataModule.DataAdapter.EndApplyChanges(ar);});}, null);} |
The next stop is the data loading code. It should look like this:
async privatevoid LoadData(object sender, RoutedEventArgs e){this.dataList.ItemsSource= await LoadDataAsync<Orders>(this.fDataModule.DataAdapter.GetTable<Orders>().Select(o => o));} async Task<DABindingList <T>> LoadDataAsync<T>(IQueryable <T> query)where T:class, new(){ Task lTaskRunner = Task.Factory.FromAsync<IQueryable []>(this.fDataModule.DataAdapter.BeginExecute, this.fDataModule.DataAdapter.EndExecute, new IQueryable []{ query }, null); return await lTaskRunner.ContinueWith<DABindingList<T>>((task)=>{return query.ToDABindingList<T>();});} |
- The LoadDataAsync
method now can be moved to the DataModule. This wasn’t possible in the previous code version because of the Dispatcher.BeginInvoke calls. - The dispatcher is not called directly.
- The same LoadDataAsync
method can be used to load any data table via DA LINQ.
These two changes hide the data access code complexity, allowing to access data in the new async/await manner.
Also take a closer look at the LoadDataAsync
Calling service methods in the new manner is very simple as well. Check out this code where the LoginEx method is called:
async publicvoid LogOn(String userId, String password, WaitCallback callback){ RemObjects.DataAbstract.Server.IBaseLoginService_Async lAsyncService =(new RemObjects.DataAbstract.Server.BaseLoginService_AsyncProxy(this.fMessage, this.fClientChannel, "LoginService")); Boolean lLoginResult = await Task.Factory.FromAsync<String, Boolean>(lAsyncService.BeginLoginEx, lAsyncService.EndLoginEx, ConnectionString, null);} |
You should be aware that there is a limitation to the Task.Factory.FromAsync method – it only allows to convert methods to tasks that have no more than 3 arguments.
Our team has plans to implement a code gen that will generate an async/await-enabled interface files for RemObjects SDK services to make it even easier to access Data Abstract and RemObjects SDK servers in the new asynchronous way.
Summary
There is no black magic behind the new async/await asynchronous model. Despite the lack of built-in support, the new asynchronous model can be easily utilized by Data Abstract and RemObject SDK-enabled applications.