Summary of Chapter 20. Async and file I/O
Note
This book was published in the spring of 2016, and has not been updated since then. There is much in the book that remains valuable, but some of the material is outdated, and some topics are no longer entirely correct or complete.
A graphical user interface must respond to user-input events sequentially. This implies that all processing of user-input events must occur in a single thread, often called the main thread or the UI thread.
Users expect graphical user interfaces to be responsive. This means that a program must process user-input events quickly. If that is not possible, then processing must be relegated to secondary threads of execution.
Several sample programs in this book have used the WebRequest
class. In this class the BeginGetResponse
method starts a worker thread, which calls a callback function when it is complete. However, that callback function runs in the worker thread, so the program must call Device.BeginInvokeOnMainThread
method to access the user interface.
Note
Xamarin.Forms programs should use HttpClient
rather than WebRequest
for accessing files over the internet. HttpClient
supports asynchronous operations.
A more modern approach to asynchronous processing is available in .NET and C#. This involves the Task
and Task<TResult>
classes, and other types in the System.Threading
and System.Threading.Tasks
namespaces, as well as the C# 5.0 async
and await
keywords. That's what this chapter focuses on.
From callbacks to await
The Page
class itself contains three asynchronous methods to display alert boxes:
DisplayAlert
returns aTask
objectDisplayAlert
returns aTask<bool>
objectDisplayActionSheet
returns aTask<string>
object
The Task
objects indicate that these methods implement the Task-based Asynchronous Pattern, known as TAP. These Task
objects are returned quickly from the method. The Task<T>
return values constitute a "promise" that a value of type TResult
will be available when the task completes. The Task
return value indicates an asynchronous action that will complete but with no value returned.
In all these cases, the Task
is complete when the user dismisses the alert box.
An alert with callbacks
The AlertCallbacks sample demonstrates how to handle Task<bool>
return objects and Device.BeginInvokeOnMainThread
calls using callback methods.
An alert with lambdas
The AlertLambdas
sample demonstrates how to use anonymous lambda functions for handling Task
and Device.BeginInvokeOnMainThread
calls.
An alert with await
A more straightforward approach involves the async
and await
keywords introduced in C# 5. The AlertAwait
sample demonstrates their use.
An alert with nothing
If the asynchronous method returns Task
rather than Task<TResult>
, then the program doesn't need to use any of these techniques if it doesn't need to know when the asynchronous task completes. The
NothingAlert
sample demonstrates this.
Saving program settings asynchronously
The SaveProgramChanges sample demonstrates the use of the SavePropertiesAsync
method of Application
to save program settings as they change without overriding the OnSleep
method.
A platform-independent timer
It's possible to use Task.Delay
to create a platform-independent timer. The
TaskDelayClock sample demonstrates this.
File input/output
Traditionally, the .NET System.IO
namespace has been the source of file I/O support. Although some methods in this namespace support asynchronous operations, most do not. The namespace also supports several simple method calls that perform sophisticated file I/O functions.
Good news and bad news
All the platforms supported by Xamarin.Forms support application local storage — storage that is private to the application.
The Xamarin.iOS and Xamarin.Android libraries include a version of .NET that Xamarin has expressly tailored for these two platforms. These include classes from System.IO
that you can use to perform file I/O with application local storage in these two platforms.
However, if you search for these System.IO
classes in a Xamarin.Forms PCL, you won't find them. The problem is that Microsoft completely revamped file I/O for the Windows Runtime API. Programs targeting Windows 8.1, Windows Phone 8.1, and the Universal Windows Platform do not use System.IO
for file I/O.
This means that you'll need to use the DependencyService
(first discussed in Chapter 9. Platform-specific API calls to implement file I/O.
Note
Portable Class Libaries have been replaced with .NET Standard 2.0 libraries, and .NET Standard 2.0 supports System.IO
types for all Xamarin.Forms platforms. It is no longer necessary to use a DependencyService
for most file I/O tasks. See File Handling in Xamarin.Forms for a more modern approach to file I/O.
A first shot at cross-platform file I/O
The
TextFileTryout sample defines an IFileHelper
interface for file I/O, and implementations of this interface in all the platforms. However, the Windows Runtime implementations don't work with the methods in this interface because the Windows Runtime file I/O methods are asynchronous.
Accommodating Windows Runtime file I/O
Programs running under the Windows Runtime use classes in the Windows.Storage
and Windows.Storage.Streams
namespaces for file I/O, including application local storage. Because Microsoft determined that any operation requiring more than 50 milliseconds should be asynchronous to avoid blocking the UI thread, these file I/O methods are mostly asynchronous.
The code demonstrating this new approach will be in a library so that it can be used by other applications.
Platform-specific libraries
It's advantageous to store reusable code in libraries. This is obviously more difficult when different pieces of the reusable code are for entirely different operating systems.
The Xamarin.FormsBook.Platform solution demonstrates one approach. This solution contains seven different projects:
- Xamarin.FormsBook.Platform, a normal Xamarin.Forms PCL
- Xamarin.FormsBook.Platform.iOS, an iOS class library
- Xamarin.FormsBook.Platform.Android, an Android class library
- Xamarin.FormsBook.Platform.UWP, a Universal Windows class library
- Xamarin.FormsBook.Platform.WinRT, a shared project for code that is common to all the Windows platforms
All the individual platform projects (with the exception of Xamarin.FormsBook.Platform.WinRT) have references to Xamarin.FormsBook.Platform. The three Windows projects have a reference to Xamarin.FormsBook.Platform.WinRT.
All the projects contain a static Toolkit.Init
method to ensure that the library is loaded if it's not directly referenced by a project in a Xamarin.Forms application solution.
The Xamarin.FormsBook.Platform project contains the new IFileHelper
interface. All the methods now have names with Async
suffixes and return Task
objects.
The Xamarin.FormsBook.Platform.WinRT project contains the FileHelper
class for the Windows Runtime.
The Xamarin.FormsBook.Platform.iOS project contains the FileHelper
class for iOS. These methods must now be asynchronous. Some of the methods use the asynchronous versions of methods defined in StreamWriter
and StreamReader
: WriteAsync
and ReadToEndAsync
. Others convert a result to a Task
object using the FromResult
method.
The Xamarin.FormsBook.Platform.Android project contains a similar FileHelper
class for Android.
The Xamarin.FormsBook.Platform project also contains a FileHelper
class that eases the use of the DependencyService
object.
To use these libraries, an application solution must include all the projects in the Xamarin.FormsBook.Platform solution, and each of the application projects must have a reference to the corresponding library in Xamarin.FormsBook.Platform.
The TextFileAsync solution demonstrates how to use the Xamarin.FormsBook.Platform libraries. Each of the projects has a call to Toolkit.Init
. The application makes use of the asynchronous file I/O functions.
Keeping it in the background
Methods in libraries that make calls to multiple asynchronous methods — such as the WriteFileAsync
and ReadFileASync
methods in the Windows Runtime FileHelper
class — can be made somewhat more efficient by using the ConfigureAwait
method to avoid switching back to the user-interface thread.
Don't block the UI thread!
Sometimes it's tempting to avoid the use of ContinueWith
or await
by using the Result
property on the methods. This should be avoided for it can block the UI thread or even hang the application.
Your own awaitable methods
You can run some code asynchronously by passing it to one of the Task.Run
methods. You can call Task.Run
within an async method that handles some of the overhead.
The various Task.Run
patterns are discussed below.
The basic Mandelbrot set
To draw the Mandelbrot set in real time, the Xamarin.Forms.Toolkit library has a Complex
structure similar to the one in the System.Numerics
namespace.
The MandelbrotSet sample has a CalculateMandeblotAsync
method in its code-behind file that calculates the basic black-and-white Mandelbrot set and uses BmpMaker
to put it on a bitmap.
Marking progress
To report progress from an asynchronous method, you can instantiate a Progress<T>
class and define your asynchronous method to have an argument of type IProgress<T>
. This is demonstrated in the MandelbrotProgress sample.
Cancelling the job
You can also write an asynchronous method to be cancellable. You begin with a class named CancellationTokenSource
. The Token
property is a value of type CancellationToken
. This is passed to the asynchronous function. A program calls the Cancel
method of CancellationTokenSource
(generally in response to an action by the user) to cancel the asynchronous function.
The asynchronous method can periodically check the IsCancellationRequested
property of CancellationToken
and exit if the property is true
, or simply call the ThrowIfCancellationRequested
method, in which case the method ends with an OperationCancelledException
.
The MandelbrotCancellation sample demonstrates the use of a cancellable function.
An MVVM Mandelbrot
The
MandelbrotXF sample has a more extensive user interface, and it's mostly based on a MandelbrotModel
and MandelbrotViewModel
classes:
Back to the web
The WebRequest
class used in some samples uses an old-fashioned asynchronous protocol called the Asynchronous Programming Model or APM. You can convert such a class to the modern TAP protocol using one of the FromAsync
methods in the TaskFactory
class. The
ApmToTap
sample demonstrates this.