Cross Platform C#
Asynchronous Operations with Xamarin
Your guide to all aspects of how Xamarin has implemented .NET 4.5/C# 5's support for asynch operations in Xamarin.iOS and Xamarin.Android.
One of the great things about the .NET Framework is that Microsoft has worked long and hard to improve many features. Since the initial release of .NET 1.0, there has been support for threading via .NET threads as well as an application-level threadpool. This provided a great starting point when compared to Visual Basic 6 and classic ASP programming. The release of.NET 4 brought significant improvements in the area of threading, asynchronous operations and parallel operations. While the improvements made working with asynchronous operations easier, new problems were introduced, since many of these operations work based on callbacks. For example:
- How should a developer handle error checking?
- The program flow tends to be non-linear. Fixing bugs can be problematic.
- It is hard for a developer to get an understanding of what is happening within an application.
The release of .NET 4.5 (and C# 5.0), in the fall of 2012, was a blockbuster update with regards to asynchronous operations and threads. Microsoft has added C# language keywords to take this non-linear callback-based program flow and turn it into a much more linear flow. Recently, Xamarin has updated Xamarin.Android and Xamarin.iOS to support async.
This article will look at how Xamarin has implemented the .NET 4.5/C# 5 support into their Xamarin.iOS and Xamarin.Android productions. There are three general areas that I'll focus on:
- A general look at the asynchronous support in Xamarin's mobile products. This includes async, await, and the implications that this has for cross-platform code.
- The new HttpClient class that is provided in .NET 4.5/Mono 3.2.
- Xamarin's extensions for asynchronous operations for Android and iOS.
Why Asynchronous Operations Are Good
Before getting into code, it's important to understand why asynchronous operations are a good thing:
- Mobile devices refresh their screen at 60 frames per second. A screen refresh will take place every 16.7 milliseconds. Movies are typically shown at a rate of 24 or 25 frames per second. This data indicates that the human eye can detect changes starting at around 20 frames per second. Anything that delays an update can cause a delay that is perceptible to the human eye. Borrowing from Miguel de Icaza, any operation that takes more than 50 milliseconds is a good candidate for running asynchronously.
- Going off the device to a remote data source is filled with delays. There are delays inherent in a wireless network, the internet, networks in general, remote data sources and other points along the way. These delays can be tremendous. Imagine downloading a set of video URLs and then downloading each video. Depending on the size of the video, the download could take almost any length of time. As a result, any operation that goes off of the device is a good candidate for an asynchronous operation.
- If the user interface thread is blocked for a period of time, a watchdog built into a device will kill the application that is blocking the UI thread.
Background on .NET Async and Await
With .NET 4.5/C# 5.0, Microsoft added the keywords async and await to improve the development experience of long running methods. Simply put:
- A method that has been marked with the async keyword can have its execution suspended at various points within its code. The suspension of the code is marked by the await operator.
- The await operator within instructs the compiler that execution cannot continue beyond an asynchronous command until the command that is awaited is finished.
- Asynchronous methods typically return Task<T>, though they may return Task (void returns) or void (for event handlers). As opposed to calling await on the async method, it is possible to call await on the return value.
For a complete discussion of async and await in .NET 4.5/Mono 3.2, start at this great explanation from Microsoft.
Asynchronous Support from Xamarin for .NET 4.5/C# 5
Xamarin has recently shipped Xamarin.iOS 6.4 and Xamarin.Android 4.8. These versions have the support for the keywords async and await. This example runs on Android and will take data from a JSON endpoint and bind it to a ListView. The data that is bound includes text and an image showing the weather for the next 14 days. The output can be seen in Figure 1.
The Activity code to display the weather is shown below. The await and async keywords are supported.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AsyncListView
{
[Activity (Label = "Weather Activity")]
public class WeatherActivity : ListActivity
{
async protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
var location = Intent.GetStringExtra("Location");
var wr = await GetData (location);
ListAdapter = new WeatherAdapter(this, wr.list);
}
async Task<WeatherReport> GetData(string location)
{
string contents;
WeatherReport res = new WeatherReport();
try
{
string Url = String.Format( "http://api.openweathermap.org/data/2.5/forecast/daily?q=" + "{0}&mode=json&units=imperial&cnt=14", location); HttpClient hc = new HttpClient();
Task<string> contentsTask = hc.GetStringAsync(Url); // async method!
// await! control returns to the caller and
// the task continues to run on another thread
contents = await contentsTask;
res = JsonConvert.DeserializeObject<WeatherReport>(contents);
Parallel.ForEach(res.list, currentWeather =>
{
var url = String.Format( "http://openweathermap.org/img/w/{0}.png", currentWeather.weather[0].icon);
var imageUrl = new Java.Net.URL(url);
Android.Graphics.Bitmap bitmap =
Android.Graphics.BitmapFactory.DecodeStream(imageUrl.OpenStream());
currentWeather.weather[0].Image = bitmap;
});
}
catch (System.Exception sysExc)
{
Console.WriteLine(sysExc.Message);
}
return res;
}
}
}
Author Note: Sometimes the API for OpenWeatherMap generates errors. If it does not work for you at any particular moment, try again later.
The code for the WeatherAdapter is shown below. The data that is returned on the async await operations runs on a separate thread, however, the data is returned on the current thread. Because of this, there is no need to do any of the RunOnUIThread operations in Android or InvokeOnMainThread operations in iOS, since the data is returned to the UI Thread. The result is that the data binding is done on the UI Thread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace AsyncListView
{
class WeatherAdapter : BaseAdapter<WeatherDay>
{
List<WeatherDay> _wd;
Activity _context;
public WeatherAdapter(Activity context, List<WeatherDay> wr) : base() {
this._context = context;
this._wd = wr;
}
public override long GetItemId(int position)
{
return position;
}
public override WeatherDay this[int position] {
get { return _wd[position]; }
}
public override Java.Lang.Object GetItem(int position)
{
return _wd[position].weather[0].description;
}
public override int Count {
get { return _wd.Count; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView; // re-use an existing view, if one is available
if (view == null) // otherwise create a new one
view = _context.LayoutInflater.Inflate( Android.Resource.Layout.ActivityListItem, null);
view.FindViewById<TextView>(Android.Resource.Id.Text1).Text =
_wd[position].weather[0].description + System.Environment.NewLine +
String.Format("High: {0} Low: {1}", _wd[position].temp.max,
_wd[position].temp.min);
var ic = view.FindViewById<ImageView>(Android.Resource.Id.Icon);
ic.SetImageBitmap(_wd[position].weather[0].Image);
return view;
}
}
}
The code for the WeatherReport is shown below. Note that this example uses the following code for the WeatherReport class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AsyncListView
{
class WeatherCity
{
public WeatherCity() {
}
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
[JsonProperty(PropertyName = "coord")]
public coord location { get; set; }
}
class coord
{
[JsonProperty(PropertyName = "lat")]
public float lat { get; set; }
[JsonProperty(PropertyName = "lon")]
public float lon { get; set; }
}
class WeatherDay
{
public WeatherDay(){
weather = new List<weatherinfo>();
}
[JsonProperty(PropertyName = "dt")]
public int dt { get; set; }
[JsonProperty(PropertyName = "temp")]
public temps temp { get; set; }
[JsonProperty(PropertyName = "pressure")]
public float pressure { get; set; }
[JsonProperty(PropertyName = "humidity")]
public int humidity { get; set; }
[JsonProperty(PropertyName = "weather")]
public List<weatherinfo> weather { get; set; }
}
class temps
{
[JsonProperty(PropertyName = "day")]
public float day { get; set; }
[JsonProperty(PropertyName = "night")]
public float night { get; set; }
[JsonProperty(PropertyName = "min")]
public float min { get; set; }
[JsonProperty(PropertyName = "max")]
public float max { get; set; }
[JsonProperty(PropertyName = "eve")]
public float eve { get; set; }
[JsonProperty(PropertyName = "morn")]
public float morn { get; set; }
}
class weatherinfo
{
[JsonProperty(PropertyName = "id")]
public int id { get; set; }
[JsonProperty(PropertyName = "main")]
public string main { get; set; }
[JsonProperty(PropertyName = "description")]
public string description { get; set; }
[JsonProperty(PropertyName = "icon")]
public string icon { get; set; }
public Android.Graphics.Bitmap Image { get; set; }
}
class WeatherReport
{
public WeatherReport()
{
list = new List<WeatherDay>();
}
[JsonProperty( PropertyName = "cod" )]
public string cod { get; set; }
[JsonProperty(PropertyName = "message")]
public string message { get; set; }
[JsonProperty(PropertyName = "city")]
public WeatherCity city { get; set; }
[JsonProperty(PropertyName = "cnt")]
public int cnt { get; set; }
[JsonProperty(PropertyName = "list")]
public List<WeatherDay> list { get; set; }
}
}
As the code shows, the inclusion of async and await allow for:
- Linear program flow
- The removal of the callbacks makes error handling easier. Note the use of the try/catch block. During the development of this sample, there were exceptions in the JSON serialization that were easily caught. Properly handling errors that are buried multiple levels deep can be hard. Using async and await make these issues easier to handle.
An issue comes up regarding how to store the image. Previously, I would have just done this with a threadpool thread in the GetView method. This is still a valid way to do things. However, the question of how to do this with a focus on cross platform code sharing comes up. There are two thoughts on this:
- Instead of having an Android Bitmap object included with the weatherinfo class, it could be moved out of that file and put into another file, and the weather info class could have a partial class definition. When the code is compiled in Android, the Bitmap could be compiled in. When the code is compiled in iOS, a UIImage could be compiled in.
- The image file could be downloaded and converted into a byte[] array. The byte array could be stored with the weatherinfo class. This keeps the class files the same across the platforms.
Support for the new HttpClient Class
One of the new features of .NET 4.5/Mono 3.2 is the inclusion of the System.Net.Http.HttpClient class. This class is designed to perform asynchronous requests via the GET, POST, PUT, & DELETE Http verbs. The method calls for this are:
- GetStringAsync
- PutAsync
- PostAsync
- DeleteAsync
There are other methods that this class exposes. Some of these methods are:
- CancelPendingRequests
- GetByteArrayAsync
- GetStreamAsync
- SendAsync
One item of note is that the System.Net.Http namespace is not included with a Xamarin.Android project and must be added as a reference.
Xamarin Asynchronous Extensions
Thinking back to the 50 millisecond rule for asynchronous operations that was previously discussed, there are some operations that are built into Android that will violate this general rule without ever going off the device. Because of this, Xamarin has extended many of the API calls with asynchronous versions. These extensions include:
- Xamarin.iOS has 174 methods that have been extended with async support.
- Xamarin.Android has 337 methods that have been extended with async support.
- Xamarin.Mobile
- Xamarin Component Store
Regarding the Xamarin async extensions, these can be found on many standard device events and methods, button clicks, and any method that make take 50 milliseconds (or more) to potentially run. In the previous example, the OnCreate() method was prefaced with the async command.
About the Author
Wallace (Wally) B. McClure has authored books on iPhone programming with Mono/Monotouch, Android programming with Mono for Android, application architecture, ADO.NET, SQL Server and AJAX. He's a Microsoft MVP, an ASPInsider and a partner at Scalable Development Inc. He maintains a blog, and can be followed on Twitter.