Creating Mobile Apps with Xamarin.Forms: Infrastructure

  • 10/1/2014

Version 6. Awaiting results

As you’ve seen, asynchronous operations can be difficult to manage. Sometimes code executes in a different order than you anticipated and some thought is required to figure out what’s going on. Asynchronous programming will likely never be quite as simple as single-threaded coding, but some of the difficulty in working with asynchronous operations has been alleviated with C# 5.0, released in 2012. C# 5.0 introduced a revolutionary change in the way that programmers deal with asynchronous operations. This change consists of a keyword named async and an operator named await.

The async keyword is mostly for purposes of backward compatibility. The await operator is the big one. It allows programmers to work with asynchronous functions almost as if they were relatively normal imperative programming statements without callback methods.

Let’s look at a generalized use of an asynchronous method that might appear in a Windows Phone 8 program. You have a method in your program that calls an asynchronous method in the operating system and sets a Completed handler implemented as a separate method. The comments indicate some other code that might appear in the method:

void SomeMethod()
{
    // Often some initialization code
    IAsyncOperation<SomeType> asyncOp = sysClass.LongJobAsync(...);
    // Some additional code
    asyncOp.Completed = MyCompletedHandler;
    // And perhaps still more code
}

The statement that sets the Completed property returns quickly while the actual asynchronous operation goes on in the background. All the code in SomeMethod will execute before the callback method is called. Here’s what the Completed handler might look like if you chose to ignore cancellations or errors encountered in the background process:

void MyCompletedHandler(IAsyncOperation<SomeType> op, AsyncStatus status)
{
   SomeType myResult = op.GetResults();
   // Code using the result of the asynchronous operation
}

The handler can also be written as a lambda function.

void SomeMethod()
{
   // Often some initialization code
   IAsyncOperation<SomeType> asyncOp = sysClass.LongJobAsync(...);
   // Some additional code
   asyncOp.Completed = (op, status) =>
   {
       SomeType myResult = op.GetResults();
       // Code using the result of the asynchronous operation
   };
   // And perhaps still more code
}

The additional code indicated with the comment “And perhaps still more code” will execute and SomeMethod will return before the code in the Completed handler executes. The order that the code appears in the method is not the same order that the code executes, and this is one reason why using lambda functions for asynchronous operations can sometimes be a bit confusing.

Here’s how SomeMethod can be rewritten using the await operator:

async void SomeMethod()
{
   // Often some initialization code
   IAsyncOperation<SomeType> asyncOp = sysClass.LongJobAsync(...);
   // Some additional code
   // And perhaps still more code
   SomeType myResult = await asyncOp;
   // Code using the result of the asynchronous operation
}

Notice that SomeMethod now includes the async modifier. This is required for backward compatibility. In versions of C# prior to 5.0, await was not a keyword so it could be used as a variable name. To prevent C# 5.0 from breaking that code, the async modifier is required to indicate a method that includes await.

The Completed handler still exists in this code, but it’s not exactly obvious where it is. It’s basically everything to the left of the await operator, and everything below the statement containing the await operator.

The C# compiler performs the magic. The compiler recognizes IAsyncOperation as encapsulating an asynchronous method call and basically turns SomeMethod into a state machine. The method executes normally up until the await operator, and then the method returns. At this point, the background process is running and other code in the program can run as well. When the background process is completed, execution of the method resumes with the assignment to the myResult variable.

If you can organize your code to eliminate the two comments labeled as “Some additional code” and “And perhaps still more code” you can skip the assignment to the IAsyncOperation object and just get the result:

async void SomeMethod()
{
   // Often some initialization code
   SomeType myResult = await sysClass.LongJobAsync(...);
   // Code using the result of the asynchronous operation
}

This is how await is customarily used—as an operator between a method that runs asynchronously and the assignment statement to save the result.

Don’t let the name of the await operator fool you! The code doesn’t actually sit there waiting. The SomeMethod method returns and the processor is free to run other code. Only when the asynchronous operation has completed does the code in SomeMethod resume execution where it left off.

Of course, any time we’re dealing with file I/O, we should be ready for problems. What happens if the file isn’t there, or you run out of storage space? Even when problems don’t occur, sometimes error results are normal: The Windows Phone version of the Exists method uses an error reported in the Completed handler to determine if the file exists or not. So how are errors handled with await?

If you use await with an asynchronous operation that encounters an error or is cancelled, await throws an exception. If you need to handle errors or cancellations, you can put the await operator in a try and catch block, looking something like this:

SomeType myresult = null;
try
{
   myresult = await sysClass.LongJobAsync(...);
}
catch (OperationCanceledException)
{
   // handle a cancellation
}
catch (Exception exc)
{
   // handle an error
}

Now let’s use await in some real code. Here’s the Windows Phone version of the old ReadAll-Text method in NoteTaker5:

public void ReadAllText(string filename, Action<string> completed)
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    IAsyncOperation<StorageFile> createOp = localFolder.GetFileAsync(filename);
    createOp.Completed = (asyncInfo1, asyncStatus1) =>
    {
        IStorageFile storageFile = asyncInfo1.GetResults();
        IAsyncOperation<IRandomAccessStreamWithContentType> openOp =
                                storageFile.OpenReadAsync();
        openOp.Completed = (asyncInfo2, asyncStatus2) =>
        {
            IRandomAccessStream stream = asyncInfo2.GetResults();
            DataReader dataReader = new DataReader(stream);
            uint length = (uint)stream.Size;
            DataReaderLoadOperation loadOp = dataReader.LoadAsync(length);
            loadOp.Completed = (asyncInfo3, asyncStatus3) =>
            {
                string text = dataReader.ReadString(length);
                dataReader.Dispose();
                completed(text);
            };
        };
    };
}

This has three nested Completed handlers. Because the method returns before even the first Completed handler executes, it is not possible to return the contents of the file from the method. The file contents must instead be returned through a function passed to the method.

Here’s how it can be rewritten using await:

public async void ReadAllText(string filename, Action<string> completed)
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    IStorageFile storageFile = await localFolder.GetFileAsync(filename);
    IRandomAccessStream stream = await storageFile.OpenReadAsync();
    DataReader dataReader = new DataReader(stream);
    uint length = (uint)stream.Size;
    await dataReader.LoadAsync(length);
    string text = dataReader.ReadString(length);
    dataReader.Dispose();
    completed(text);
}

Notice the async modifier on the method. The async modifier does not change the signature of the method, so this method is still considered a proper implementation of the ReadAllText method in the IFileHelper interface.

The await operator appears three times; the first two times on methods that return an object and the third time on the LoadAsync method that just performs an operation without returning anything.

Behind the scenes, the C# compiler divides this method into four chunks of execution. The method returns to the caller (the Note class) at the first await operator and then resumes when the GetFileAsync method has completed, leaving again at the next await, and so on.

Actually, it’s possible for the ReadAllText method to execute sequentially from start to finish. If the asynchronous methods complete their operations before the await operator is evaluated, execution just continues as if it were a normal function call. This is a performance optimization that often plays a role in the relatively fast solid state file I/O on mobile devices.

Let’s improve this ReadAllText method a bit. The DataReader class implements the IDisposable interface and includes a Dispose method. Failing to call Dispose can leave the file open. Calling Dispose also closes the IRandomAccessStream on which the DataReader is based. IRandomAccessStream also implements IDisposable.

It’s a good idea to enclose IDisposable objects in using blocks. This ensures that the Dispose method is automatically called even if an exception is thrown inside the block. A better implementation of ReadAllText is this:

public async void ReadAllText(string filename, Action<string> completed)
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    IStorageFile storageFile = await localFolder.GetFileAsync(filename);
    using (IRandomAccessStream stream = await storageFile.OpenReadAsync())
    {
        using (DataReader dataReader = new DataReader(stream))
        {
            uint length = (uint)stream.Size;
            await dataReader.LoadAsync(length);
            string text = dataReader.ReadString(length);
            completed(text);
        }
    }
}

Now the explicit call to Dispose is not required.

Of course, we still have the annoyance of passing a callback function to the ReadAllText method. But what’s the alternative? If the method actually returns to the caller at the first await operator, how can the method return the text contents of the file? Trust the C# compiler. If it can implement await, surely it can also allow you to create your own asynchronous methods.

Let’s replace ReadAllText with a method named ReadTextAsync. The new name reflects the fact that this method is itself asynchronous and can be called with the await operator to return a string with the contents of the file.

To do this, the ReadTextAsync method needs to return a Task object. The Task class is defined in System.Threading.Tasks and is the standard .NET representation of an asynchronous operation. The Task class is quite extensive, but only a little bit of it is necessary in this context. The System.Threading.Tasks namespace actually defines two Task classes:

  • Task for asynchronous methods that return nothing
  • Task<TResult> for asynchronous methods that return an object of type TResult.

These are the .NET equivalences of the Windows 8 IAsyncAction and IAsyncOperation<TResult> interfaces, and there are extension methods that convert the Task objects to IAsyncAction and IAsyncOperation<TResult> objects.

Instead of returning void, this new method can return Task<string>. The callback argument isn’t required, and inside the method, the file’s contents are returned as if this were a normal method:

public async Task<string> ReadTextAsync(string filename)
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    IStorageFile storageFile = await localFolder.GetFileAsync(filename);
    using (IRandomAccessStream stream = await storageFile.OpenReadAsync())
    {
        using (DataReader dataReader = new DataReader(stream))
        {
            uint length = (uint)stream.Size;
            await dataReader.LoadAsync(length);
            return dataReader.ReadString(length);
        }
    }
}

The compiler handles the rest. The return statement seems to return a string object, which is the return value of the ReadString method, but the C# compiler automatically wraps that value in a Task<string> object actually returned from the method at the execution of the first await operator.

This ReadTextAsync method can now be called using an await operator.

Of course, redefining the method signature of these file I/O functions has an impact throughout the program. If we want to continue to use DependencyService to call platform-independent methods—and that is highly desirable—the iOS and Android methods should have the same signature. This means that the NoteTaker6 version of IFileHelper consists of these three asynchronous methods:

using System.Threading.Tasks;

namespace NoteTaker6
{
    public interface IFileHelper
    {
        Task<bool> ExistsAsync(string filename);

        Task WriteTextAsync(string filename, string text);

        Task<string> ReadTextAsync(string filename);
    }
}

There are no longer any callback functions in the methods. The Task object has its own callback mechanism.

Here’s the complete Windows Phone version of the FileHelper implementation of IFileHelper:

using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
using Xamarin.Forms;

[assembly: Dependency(typeof(NoteTaker6.WinPhone.FileHelper))]

namespace NoteTaker6.WinPhone
{
    class FileHelper : IFileHelper
    {
        public async Task<bool> ExistsAsync(string filename)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;

            try
            {
                await localFolder.GetFileAsync(filename);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public async Task WriteTextAsync(string filename, string text)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            IStorageFile storageFile =
                    await localFolder.CreateFileAsync(filename,
                                CreationCollisionOption.ReplaceExisting);

            using (IRandomAccessStream stream =
                    await storageFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                using (DataWriter dataWriter = new DataWriter(stream))
                {
                    dataWriter.WriteString(text);
                    await dataWriter.StoreAsync();
                }
            }
        }

        public async Task<string> ReadTextAsync(string filename)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            IStorageFile storageFile = await localFolder.GetFileAsync(filename);
            using (IRandomAccessStream stream = await storageFile.OpenReadAsync())
            {
                using (DataReader dataReader = new DataReader(stream))
                {
                    uint length = (uint)stream.Size;
                    await dataReader.LoadAsync(length);
                    return dataReader.ReadString(length);
                }
            }
        }
    }
}

Notice the use of the try and catch block on the await operation in the ExistsAsync method.

The WriteTextAsync method doesn’t return a value, and the return value of the method is simply Task. Such a method doesn’t require an explicit return statement. A method that returns Task<TResult> needs a return statement with a TResult object. In either case, all the asynchronous calls in the method should be preceded with await. (There are alternatives but they are somewhat more complicated. Asynchronous methods without any await operators can also be handled somewhat differently as you’ll see shortly.)

For the iOS and Android versions, the methods now need to return Task and Task<TResult> objects, but up to this point the methods themselves haven’t been asynchronous. One solution is to switch to using asynchronous file I/O, at least in part. Many of the I/O methods in System.IO have asynchronous versions. That’s what’s been done here with the WriteTextAsync and ReadTextAsync methods:

using System;
using System.IO;
using System.Threading.Tasks;
using Xamarin.Forms;

[assembly: Dependency(typeof(NoteTaker6.iOS.FileHelper))]

namespace NoteTaker6.iOS
{
    class FileHelper : IFileHelper
    {
        public Task<bool> ExistsAsync(string filename)
        {
            string filepath = GetFilePath(filename);
            bool exists = File.Exists(filepath);
            return Task<bool>.FromResult(exists);
        }

        public async Task WriteTextAsync(string filename, string text)
        {
            string filepath = GetFilePath(filename);
            using (StreamWriter writer = File.CreateText(filepath))
            {
                await writer.WriteAsync(text);
            }
        }

        public async Task<string> ReadTextAsync(string filename)
        {
            string filepath = GetFilePath(filename);
            using (StreamReader reader = File.OpenText(filepath))
            {
                return await reader.ReadToEndAsync();
            }
        }

        string GetFilePath(string filename)
        {
            string docsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            return Path.Combine(docsPath, filename);
        }
    }
}

However, there is no asynchronous version of the File.Exists method, so a Task<bool> is simply constructed from the result using the static FromResult method.

The Android implementation of FileHelper is the same as the iOS version. The PCL project in NoteTaker6 has a new static FileHelper method that caches the IFileHelper object and hides all the DependencyService calls:

using System.Threading.Tasks;
using Xamarin.Forms;

namespace NoteTaker6
{
    static class FileHelper
    {
        static IFileHelper fileHelper = DependencyService.Get<IFileHelper>();

        public static Task<bool> ExistsAsync(string filename)
        {
            return fileHelper.ExistsAsync(filename);
        }

        public static Task WriteTextAsync(string filename, string text)
        {
            return fileHelper.WriteTextAsync(filename, text);
        }

        public static Task<string> ReadTextAsync(string filename)
        {
            return fileHelper.ReadTextAsync(filename);
        }
    }
}

These methods simply return the same return values as the underlying asynchronous methods.

The Note class is mostly the same as before, but the Save and Load methods are now SaveAsync and LoadAsync:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace NoteTaker6
{
    class Note : INotifyPropertyChanged
    {
        ...

        public Task SaveAsync(string filename)
        {
            string text = this.Title + "\n" + this.Text;
            return FileHelper.WriteTextAsync(filename, text);
        }

        public async Task LoadAsync(string filename)
        {
            string text = await FileHelper.ReadTextAsync(filename);

            // Break string into Title and Text.
            int index = text.IndexOf('\n');
            this.Title = text.Substring(0, index);
            this.Text = text.Substring(index + 1);
        }

        ...
    }
}

Neither of these methods returns a value, but because they are asynchronous, the methods return a Task object that can be awaited. There are a couple ways to deal with such methods. The SaveAsync method simply returns the return value from FileHelper.WriteTextAsync, which is also a Task object. The LoadAsync method has no return statement, although it could surely end with an empty return statement. The SaveAsync method could have an implicit empty return statement but the WriteTextAsync call would have to be preceded with await, and because of the existence of that await operator, the method would need an async modifier:

public async Task SaveAsync(string filename)
{
    string text = this.Title + "\n" + this.Text;
    await FileHelper.WriteTextAsync(filename, text);
}

These new function definitions require changes to the code in the NoteTaker6Page constructor as well, and you have a couple choices. You can define the Clicked handler for the Load button like so:

loadButton.Clicked += (sender, args) =>
    {
        note.LoadAsync(FILENAME);
    };

You’ll get a warning from the compiler that you might consider using the await operator. But strictly speaking, you don’t need to. What happens is that the Clicked handler calls LoadAsync but doesn’t wait for it to complete. The Clicked handler returns back to the button that fired the event before the file has been loaded.

You can use await in a lambda function but you must precede the argument list with the async modifier:

loadButton.Clicked += async (sender, args) =>
    {
        await note.LoadAsync(FILENAME);
    };

For the Save button, you probably don’t want to enable the Load button until the save operation has completed, so you’ll want the await in there:

saveButton.Clicked += async (sender, args) =>
    {
        await note.SaveAsync(FILENAME);
        loadButton.IsEnabled = true;
    };

Actually, if you start thinking about asynchronous file I/O, you might start getting nervous—and justifiably so. For example, what if you press the Save button and, while the file is still in the process of being saved, you press the Load button? Will an exception result? Will you only load half the file because the other half hasn’t been saved yet?

It’s possible to avoid such problems on the user-interface level. If you want to prohibit a button press at a particular time, you can disable the button. Here’s how the program can prevent the buttons from interfering with each other or with themselves:

saveButton.Clicked += async (sender, args) =>
    {
        saveButton.IsEnabled = false;
        loadButton.IsEnabled = false;
        await note.SaveAsync(FILENAME);
        saveButton.IsEnabled = true;
        loadButton.IsEnabled = true;
    };

loadButton.Clicked += async (sender, args) =>
    {
        saveButton.IsEnabled = false;
        loadButton.IsEnabled = false;
        await note.LoadAsync(FILENAME);
        saveButton.IsEnabled = true;
        loadButton.IsEnabled = true;
    };

It’s unlikely that you’ll run into problems with these very small files and solid-state memory, but it never hurts to be too careful.

As you’ll recall, the Load button must be initially disabled if the file doesn’t exist. The await operator is a full-fledged C# operator, so you should be able to do something like this:

Button loadButton = new Button
{
    Text = "Load",
    IsEnabled = await FileHelper.ExistsAsync(FILENAME),
    HorizontalOptions = LayoutOptions.CenterAndExpand
};

Yes, this works!

And yet, it doesn’t. The problem is that the method that contains this code must have an async modifier and the async modifier is not allowed on a constructor. But there’s a way around this restriction. You can put all the initialization code in a method named Initialize with the async modifier and then call it from the constructor:

public NoteTaker6Page()
{
    Initialize();
}

async void Initialize()
{
    ...
}

The Initialize method will execute up to the point of the await operator and then return back to the constructor, which will then return back to the code that instantiated the class. The remainder of the Initialize method continues after the ExistsAsync method returns a value and the IsEnabled property is set.

But in the general case, do you want a good chunk of your page initialization to be delayed until a single property is set that has no other effect on the page? Probably not.

Even with the availability of await, there are times when it makes sense to devote a completed handler to a chore. When an asynchronous method returns a Task object, the syntax is a little different for specifying a completed handler, but here it is:

FileHelper.ExistsAsync(FILENAME).ContinueWith((task) =>
    {
        loadButton.IsEnabled = task.Result;
    });

Here’s the complete NoteTaker6Page class:

class NoteTaker6Page : ContentPage
{
    static readonly string FILENAME = "test.note";

    public NoteTaker6Page()
    {

        // Create Entry and Editor views.
        Entry entry = new Entry
        {
            Placeholder = "Title (optional)"
        };

        Editor editor = new Editor
        {
            Keyboard = Keyboard.Create(KeyboardFlags.All),
            BackgroundColor = Device.OnPlatform(Color.Default,
                                                Color.Default,
                                                Color.White),
            VerticalOptions = LayoutOptions.FillAndExpand
        };

        // Set data bindings.
        Note note = new Note();
        this.BindingContext = note;
        entry.SetBinding(Entry.TextProperty, "Title");
        editor.SetBinding(Editor.TextProperty, "Text");

        // Create Save and Load buttons.
        Button saveButton = new Button
        {
            Text = "Save",
            HorizontalOptions = LayoutOptions.CenterAndExpand
        };

        Button loadButton = new Button
        {
            Text = "Load",
            IsEnabled = false,
            HorizontalOptions = LayoutOptions.CenterAndExpand
        };

        // Set Clicked handlers.
        saveButton.Clicked += async (sender, args) =>
            {
                saveButton.IsEnabled = false;
                loadButton.IsEnabled = false;
                await note.SaveAsync(FILENAME);
                saveButton.IsEnabled = true;
                loadButton.IsEnabled = true;
            };

        loadButton.Clicked += async (sender, args) =>
            {
                saveButton.IsEnabled = false;
                loadButton.IsEnabled = false;
                await note.LoadAsync(FILENAME);
                saveButton.IsEnabled = true;
                loadButton.IsEnabled = true;
            };

        // Check if the file is available.
        FileHelper.ExistsAsync(FILENAME).ContinueWith((task) =>
            {
                loadButton.IsEnabled = task.Result;
            });

        // Assemble page.
        this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 0);

        this.Content = new StackLayout
        {
            Children =
            {
                new Label
                {
                    Text = "Title:"
                },
                entry,
                new Label
                {
                    Text = "Note:"
                },
                editor,
                new StackLayout
                {
                    Orientation = StackOrientation.Horizontal,
                    Children =
                    {
                        saveButton,
                        loadButton
                    }
                }
            }
        };
    }
}

With no error handling, the program is implicitly assuming that there will be no problems encountered when loading and saving files. Error handling can be implemented by enclosing any asynchronous method call with await in a try and catch block. If you want to deal with errors “silently” without informing the user, you can implement this check in the Note class. If you need to display an alert to the user, it makes more sense to perform the check in the ContentPage derivative.