C # async for iOS and Android

Original author: Miguel de Icaza, Craig Dunn
  • Transfer
Xamarin has added support for C # 5 Async / await on iOS and Android. In addition to the .NET Async base classes, 174 asynchronous methods appeared in Xamarin.iOS and 337 in Xamarin.Android. Xamarin Mobile , which provides cross-platform access to the address book, camera and geolocation, . Components in full add support for async, for example, cloud backend Parse .

Under the cat decoding and translation of a webinar about this significant event.




Programmers want to make responsive applications, so they use asynchronous programming.

A responsive interface does not respond to user actions for longer than 64 milliseconds, this is not so simple. Especially if the reaction to user actions requires long operations. The easiest option is to start a long operation in another thread. And if you use threads, you need to come up with your own system to coordinate their actions. There are many different approaches and literature on this subject, but each time you need to implement it anew.

Almost any operation on a smartphone can be long: working with the file system, scaling the picture, querying the database. And when you download something over the network, you generally cannot predict how long it will take. And to respond to the user no later than 64 milliseconds, we came up with callbacks. They are now very popular with NodeJS.

If you use a lot of callbacks, the code becomes unreadable and difficult to modify, this is called Callback Hell .



The problem is that the execution order of this piece of code is not obvious and difficult to understand. We cannot think of functions, loops, function calls, we begin to think of small blocks of code that call other small blocks of code.

Adding new functions to such code is a very difficult task, my head hurts when I just think about changing this method. And the most interesting is that we are not processing errors yet. I do not want to do this, I give up.


Edsger Dijkstra
Наш мозг заточен для понимания статических структур, наши возможности представлять процессы развивающиеся во времени развиты плохо. Поэтому мы должны сократить разницу между кодом программы и процессом ее выполнения, чтобы понять как будет выполняться программа по ее коду было как можно проще.
He meant using GOTO, we faced the same problem when using callbacks. But progress does not stand still, we create new idioms, new languages, teach the compiler to do the work for us. And async / await in C # is the new idiom that can replace callbacks.

async Task SnapAndPostAsync()
{
    try {
        Busy = true;
        UpdateUIStatus ("Taking a picture");
        var picker = new Xamarin.Media.MediaPicker ();
        var mFile = await picker.TakePhotoAsync (new Xamarin.Media.StoreCameraMediaOptions ());
        var tagsCtrl = new GetTagsUIViewontroller (mFile.GetStream ());
        // Call new iOS await API
        await PresentViewControllerAsync (tagsCtrl, true);
        UpdateUIStatus ("Submitting picture to server");
        await PostPicToServiceAsync (mFile.GetStream (), tagsCtrl.Tags);
        UpdateUIStatus ("Success");
    } catch (OperationCanceledException) {
        UpdateUIStatus ("Canceled");
    } finally {
        Busy = false;
    }
}

Here is an example of using async / await. This code looks consistent and understandable, adding new features is easy. And the whole difference is that I added the word async to the function header, which marks the method as asynchronous and then used await. These keywords tell the compiler: "please do the work for the developer, rewrite this code, divide it into several independent pieces." In fact, the output will be the same callbacks, the same state control, only made automatically. And you can read the code sequentially, as Dijkstra bequeathed.

In the example, we used the type Task . It helps us manage the operations taking place in the background. Task encapsulates the status of the task (in the process, completed, canceled), the result and exceptions that occurred during the execution. There are two types of Task: not returning a value and returning a value of type T.
There are many interesting things you can do with Task. If you have several tasks, you can wait for the completion of any of them or all together. Or chain and execute sequentially.

You can create a Task consisting of other Task. For example, you can run three tasks to receive data from different server mirrors and wrap them in one task, which will be completed as soon as any of the three is completed and returns its result.

Let's look at another example. I really like him for his consistency. This method downloads the file, if it is already on the disk, we ask the user to overwrite the file or not, if he says “yes” - overwrite the file, otherwise we stop working.

async Task DownloadFile (Uri uri, string target)
{
    if (File.Exists (target)){
        switch (await ShowAlert ("File Exists", "Do you want to overwrite the file?", "yes", "no")){
        case 0:
            break;
        case 1:
            return;
        }
    }
    var stream = await Http.Get (uri);
    await stream.SaveTo(target);
}


As you remember in iOS and Android, the result of a system message comes to us in a callback, that is, without async we had to create a message, configure it, assign a callback. In the case of async, the compiler does all this for us. And that is wonderful.

Let's see how ShowAlert is implemented inside Xamarin.
public static Task<int> ShowAlert(string title, string message, params string [] buttons)
{
    var tcs = new TaskCompletionSource<int> ();
    var alert = new UIAlertView {
            Title = title,
            Message = message
    };
    foreach (var button in buttons)
            alert.AddButton (button);
    alert.Clicked += (s, e) => tcs.TrySetResult (e.ButtonIndex);
    alert.Show ();
    return tcs.Task;
}

Pay attention to the third line from the end, this is the place where magic happens. So we return the result in asynchronous methods. Of course, displaying a system message is a very simple function, but I think it will give you inspiration to write your own.

As I mentioned, Xamarin supports Async .NET5 base classes and specialized methods for mobile platforms. To be precise, Xamarin.iOS provides 174 asynchronous methods, and Xamarin.Android - 337. In addition, Xamarin Mobile has become asynchronous , which provides cross-platform access to the address book, camera and geolocation. Many components also become asynchronous, such as the cloud backend parse. .

If an API method call can take more than 50 milliseconds, we make its asynchronous version. Depending on the context, you can use either a synchronous or asynchronous version of the method.

Support for async / await is exactly the situation when Xamarin greatly simplifies working with iOS and Android platforms and ceases to be just a wrapper.

(approx. lane begins part of Craig Dunn, it seems he is retelling Task-based Asynchronous Pattern )
Let's look at larger examples. All code is available on Github .

Pay attention to nested callbacks, error handling in several places, switching to the UI stream when we need to update the user interface. Pff. The code is difficult to read and, especially, modifications.

public void DownloadHomepage()
{
	var webClient = new WebClient();

	webClient.DownloadStringCompleted += (sender, e) => {
		if(e.Cancelled || e.Error != null) {
			// do something with error
		}
		string contents = e.Result;

		int length = contents.Length;
		InvokeOnMainThread (() => {
			ResultTextView.Text += "Downloaded the html and found out the length.\n\n";
		});
		webClient.DownloadDataCompleted += (sender1, e1) => {
			if(e1.Cancelled || e1.Error != null) {
				// do something with error
			}
			SaveBytesToFile(e1.Result, "team.jpg");

			InvokeOnMainThread (() => {
				ResultTextView.Text += "Downloaded the image.\n";
				DownloadedImageView.Image = UIImage.FromFile (localPath);
			});

			ALAssetsLibrary library = new ALAssetsLibrary();     
			var dict = new NSDictionary();
			library.WriteImageToSavedPhotosAlbum (DownloadedImageView.Image.CGImage, dict, (s2,e2) => {
				InvokeOnMainThread (() => {
					ResultTextView.Text += "Saved to album assetUrl\n";
				});
				if (downloaded != null)
					downloaded(length);
			});
		};
		webClient.DownloadDataAsync(new Uri("http://xamarin.com/images/about/team.jpg"));
	};

	webClient.DownloadStringAsync(new Uri("http://xamarin.com/"));
}


Same thing using async / await:

public async Task<int> DownloadHomepageAsync()
{
	try {
		var httpClient = new HttpClient();

		Task<string> contentsTask = httpClient.GetStringAsync("http://xamarin.com"); 

		string contents = await contentsTask;

		int length = contents.Length;
		ResultTextView.Text += "Downloaded the html and found out the length.\n\n";

		byte[] imageBytes  = await httpClient.GetByteArrayAsync("http://xamarin.com/images/about/team.jpg"); 
		SaveBytesToFile(imageBytes, "team.jpg");
		ResultTextView.Text += "Downloaded the image.\n";
		DownloadedImageView.Image = UIImage.FromFile (localPath);

		ALAssetsLibrary library = new ALAssetsLibrary();     
		var dict = new NSDictionary();
		var assetUrl = await library.WriteImageToSavedPhotosAlbumAsync 
											(DownloadedImageView.Image.CGImage, dict);
		ResultTextView.Text += "Saved to album assetUrl = " + assetUrl + "\n";

		ResultTextView.Text += "\n\n\n" + contents; // just dump the entire HTML
		return length; 
	} catch {
		// do something with error
		return -1;
	}
}


Have you compared? The new code is linear, easy to read, error handling is collected in one place.

You may also notice HttpClient, a new API object available in Xamarin. In the code, we are trying to download the html of the xamarin.com home page using it. After calling GetStringAsync, a parallel thread starts immediately, which downloads html from the site. And it returns to us Task, a link to this operation. When we call await, we say that we cannot continue without the result of this operation, control is transferred to the main process. When the line is received, control returns to our method on the next line after await, we calculate its length and update the user interface.

When we call httpClient.GetByteArrayAsync, we do not create an intermediate Task, but immediately wait for the result of the request. Downloading the image will occur in another background process and when it ends the control will return to our method, we will save the image and update the user interface.

The code is consistent and understandable.

I did not do any error handling, but it is not difficult. It does not matter if an error occurs in the method code or in the HttpClient background thread, we will get it in the catch block. The compiler does this work for us.

I marked the different operations in the code with colors, let's compare the old and new versions:


Notice how inconsistent the old code is, how error handling and access to the main thread are spread throughout the code. The async / await version is much better in terms of readability and code support.

Going to async / await


So, how do you switch to asynchronous mobile programming? Use the async modifier for methods, lambda expressions, and anonymous functions so that the compiler generates asynchronous code for them. Add the Async suffix to the names of asynchronous methods, this will help other developers distinguish them from synchronous ones.


Async методы могут возвращать void, Task или Task. Void is applicable only to event handlers. Если ваш метод возвращает значение используйте Task, иначе — просто Task. Если вы используйте Task просто возвращайте значение как из обычного метода, компилятор сделает остальное.

Await can only be used on methods marked async. Await cannot be used in Catch and Finally blocks.

Error processing


If you called await for a task, any exceptions that occur inside will be thrown into the method waiting for the task.

Note: asynchronous methods returning void have nowhere to redirect exceptions, they will appear in the current thread and lead to the application crashing.

try {
    //исключения произошедшие в t будут переброшены наверх
    await t;
    ResultTextView.Text += "** Downloaded " + t.Result.Length + " bytes\n";
}
catch(OperationCancelledException) {//если t был отменен}
catch(Exception exc) {
    //поймает в том числе исключения в t
    ResultTextView.Text += "--Download Error: " + exc.Messsage + "\n";
}


Cancel tasks


An active task can be canceled, this is especially important for long tasks, such as working with a network. You can decide to cancel the task from the code or offer the user the ability to cancel too long operations.

Cancellation is implemented by passing the Cancellation token parameter to the asynchronous method. You can transfer the created token to other tasks, so you can cancel task groups.

var cts = new CancellationTokenSource();
var intResult = await DownloadHomepageAsync(cts.Token);
//позже в приложении или даже в другом потоке
cts.Cancel();


Progress display


The task can inform the waiting method about the progress of the operation, for example, transmit the current number of bytes downloaded.
To do this, you need to overload the asynchronous method with the version receiving IProgress.

public class Progress<T> : IProgress<T>
{
    public Progress;
    public Progress(Action<T> handler);
    protected virtual void OnReport(T value);
    public event EventHandler<T> ProgressChanged; //событие вызывается при обновлении прогресса задачей
}


Task combinations


Several tasks can be combined into one using the methods Task.WhenAny and Task.WhenAll . For example, you download several files by combining the tasks of downloading files into one, you can wait until all of them are downloaded or continue after the first file downloaded.

while(tasks.Count > 0) {
    //создаем задачу, которая завершится когда любая из задач будет завершена
    //t — первая выполненная задача
    var t = await Task.WhenAny(tasks);
    tasks.Remove(t);
    try {
        await t; //получаем результат выполнения t и исключения
        ResultTextView.Text += "++ Downloaded " + t.Result.length + " bytes\n";
    }
   catch(OperationCancelledException) {}
   catch(Exception exc) {
        //исключения сработавшие во время выполнения t
        ResultTextView.text += "-- Download Error: " + exc.Message + "\n";
   }
}


In this example, we download three pictures from the site, a link to one of them will cause a 403 error. So, add all the tasks to the tasks list and process them in a loop until the list is empty. We create a composite task through Task.WhenAny and wait for it to complete, it will end when any of the nested tasks are completed and return it to us in t. Delete the completed task from the list and execute wait t to get the result of the task. Exceptions that have occurred during the execution of t will pop up after await. Combinations of tasks is a powerful tool for operations of the same type, take a look at it.

Read more about C # async in the Microsoft documentation , support for the iOS and Android APIs on the Xamarin website . Sample applications in repositories for webinar .


Subscribe to our habr-blog (the button in the upper right). Every Thursday, useful articles about mobile development, marketing and the mobile studio business. The following article (September 12) “How sales are arranged in a mobile studio”: our salesman’s confession about processes, reports, a megaplan, a self-written CRM and the path of his professional growth.