- Mastering Cross:Platform Development with Xamarin
- Can Bilgin
- 958字
- 2021-07-09 20:05:19
Chapter 3. Asynchronous Programming
This chapter deep-pes into the asynchronous and multithreaded programming concepts. We will discuss platform-specific problems and give an in-depth description of how threading scenarios are executed on different platforms. The chapter is pided into following sections:
- Multithreading on Xamarin
- Asynchronous methods
- Parallel execution
- Patterns and best practices
- Background tasks
Multithreading on Xamarin
Xamarin platforms together with Windows Runtime follow the basic principles of a single-threaded apartment model. In this model, in simple terms, a process is assigned a single thread which acts as the main trunk for all the other possible branches to be created from and yield back to.
While developers still have the ability to create and consume multiple threads, in modern applications on Xamarin target platforms, this model has been extended with concurrency implementations that delegate the responsibility of thread management to runtime and allow the developer only to define execution blocks which may or may not be executed on a separate thread.
Single thread model
In Android and iOS, each mobile application is started and run on a single thread that is generally referred to as the main or the UI thread. Most of the UI interaction and process lifecycle event handlers and delegates are executed on this thread.
In this model, developers' main concern should be keeping the main thread accessible to UI interaction as long as possible. If we were to execute a blocking call on this thread, it immediately would be reflected to the user as a screen freeze or an application nonresponsive error, which will inevitably get terminated by the so-called watch-dog implementation of the underlying platform. In addition to the platform-specific restrictions, users also expect a responsive UI at all times and cannot tolerate frozen screens even for a fraction of a second. If the screen freeze lasts any longer, they will try to forcefully terminate the application (see the Feedback section of Chapter 7, View Elements).
Developers can still create, consume, and monitor other threads from the main thread. It is possible to use background threads and invoke long running processes in the background. For this purpose, the System.Threading
namespace and threading related classes are available on Xamarin.iOS and Xamarin.Android projects. Moreover, each platform has its own threading implementation under the hood.
For example, let's imagine we want to execute a long running process and we do not want this method to block the UI thread. With classic threading, the implementation would look similar to:
//var threadStart = new ThreadStart(MyLongRunningProcess); //(new Thread(threadStart)).Start(); // Or simply (new Thread(MyLongRunningProcess)).Start();
Each Thread
can give information about the current execution state, and it can be canceled, started, interrupted, or even joined by another thread. Developers can use the threading model for throttling their application and/or executing their code more efficiently without committing the cardinal sin of blocking the UI thread.
It might get a little complicated when the process you are executing on a separate thread needs to update a UI component. This would be a cross-thread violation.
For instance, if we wanted to update a UI component from a separate thread in an Android activity, we would need to execute it on the activity as follows (using Activity.RunOnUiThread
):
this.RunOnUiThread(() => { UpdateUIComponent(); });
The same execution on iOS would look similar to (using NSObject.InvokeOnMainThread
):
this.InvokeOnMainThread(() => { UpdateUIComponent(); });
For reference, on Windows Runtime the same execution would look like this:
CoreApplication.MainView .CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { UpdateUIComponent(); });
The implementation in classic threading gets even more complex when there is an exception or the operation has to be canceled, not to mention the fact that synchronization between threads and thread-safe data flow is completely left to developers or third-party implementations.
Another important mishap of using the System.Threading
namespace and the classic threading model in Xamarin, is that this namespace and thread-related classes cannot be used in PCLs.
Task-based Asynchronous Pattern
Since the introduction of the Tasks framework in .NET 4.0 and its later adoption by Mono, the Task-based Asynchronous Pattern (TAP) has become de-facto the main implementation strategy for mobile applications. While providing the required abstraction from the treading structure, it also gives the development teams the opportunity to create easily readable, manageable, and scalable application projects. As mentioned earlier, since each Xamarin target platform has the threading implemented according to the underlying runtime, this abstraction that the Tasks framework provides makes it the perfect candidate for asynchronous implementations in cross-platform projects and an invaluable part of portable class libraries.
In this pattern, each execution block is represented by a Task
or a Task<T>
according to the return value of the block (for example, if the block is returning void, it should be converted to return Task
and if the block is returning an int
, the method signature should be Task<int>
). Tasks can be executed either synchronously or asynchronously, can be awaited for a result or executed as a promise with a callback on completion, can be pushed to another thread-pool or executed on the main thread taking processor time when available.
Tasks are especially suited for computationally intensive operations, since they provide excellent control over when and how the asynchronous method is executed. Cancellation and progress support on these methods makes long running processes easily manageable.
Concurrency model on iOS
Concurrency and operation blocks on iOS runtime are Apple's take on the same issues that the Tasks framework is trying to resolve. In essence, the Tasks framework and concurrency model on iOS are the solution to creating multitasking, robust, and easily scalable applications by creating an abstraction layer so that applications do not directly manage threads, but let the operating system decide on where and when to execute operations.
The iOS runtime uses operation or dispatch queues to asynchronously dispatch tasks in a first-in-first-out (FIFO) manner. This approach provides automatic thread-pool management as well as a simple programming interface.
While the iOS runtime constructs such as NSOperation
, NSBlockOperation
, and NSOperationQueue
are implemented in the Xamarin.iOS framework and are ready to use, the implementations would only be targeting the iOS platform while Tasks can be used on all three platforms.