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.