Chapter 4. Asynchronous Programming

In this chapter, we will look at the advanced techniques that help us execute asynchronous code—one of the most important components of Dart. Asynchronous programming is a standard programming paradigm and together with object-oriented principles, it plays an important role in the development of applications. In this chapter, we will cover the following topics:

  • Event-driven architecture
  • The Dart VM execution model
  • Future
  • Zone
  • Isolates

Call-stack architectures versus event-driven architectures

For a better understanding of asynchronous programming in Dart, we will discuss call-stack and event-driven architectures.

Call-stack architectures

Traditionally, programs are built on the concept of a call stack. This concept is pretty straightforward because a program is basically a path of execution and invocation of sequential operations. Every operation can invoke another operation. At the time of invocation, a program creates a context for the callee operation. The caller operation will wait for the callee operation to return and the program will restore the context of it. Finally, the caller continues with their next operation. The callee operation might have executed another operation on its own behalf.

The program creates a call stack to coordinate and manage the context of each call. The basic primitives of this concept are calls. All calls in the program are tightly coupled, because the program knows which operation must be called after the current one and can share the same memory. The call-stack architecture is very popular and pervasive because it is very similar to the architecture of processors.

Event-driven architectures

Event-driven architecture is the exact opposite of the call-stack concept. The basic primitives of this concept are events. The system dispatches events and transmits them among loosely coupled software components and services. The benefits of event-driven architecture (EDA) are as follows:

  • It helps utilize existing resources efficiently
  • It is easy to extend, evolve, and maintain implementation, which reduces the cost of maintenance
  • It allows the exchange of events in an asynchronous manner that prevents blocking or waiting in queue
  • In event-driven architecture, the producers and consumers are loosely coupled

Interaction between the components is limited to the publisher and one or many consumers. The publisher is free of concurrency issues and synchronization problems. The consumer can be changed at any time as the producers and consumers are loosely coupled.

Note

Event-driven architecture is the right approach to build loosely coupled asynchronous systems.

The Dart VM execution model

Dart relies on event-driven architecture, which is based on a single-threaded execution model with a single event loop and two queues. Dart still provides a call stack. However, it uses events to transport context between the producers and consumers. The event loop is backed by a single thread, so no synchronization and locks are required at all.

Note

The event loop blocked with the running operation blocks the entire application.

A combination of the single-threaded execution model and asynchronous operations allows an application to perform more efficiently and is less resource intensive.

The main part of Dart VM is an event loop. Independent pieces of code can register callback functions as event handlers for certain types of events. A callback is the name given to the function that is passed as an argument of another function and is invoked in future after the event occurs in another function. Events from the timer, mouse events, events of input and output, and many others occurring in the system are registered in the event queue. Event loop sequentially processes the queued events by executing the associated callback functions that have been registered.

Note

Callbacks must be short-running functions to prevent blocking of the event loop.

Dart supports anonymous functions and closures to define callbacks. The closure has a state bind to the callback function. Once the callback is executed, the state is available in the event loop. Callbacks are never executed in parallel because of single-threaded execution, so the occurrence of a deadlock is impossible. Once the callback has been executed, the event-loop fetches the next event from the event queue and applies its callback.

Dart introduced a new term for tasks that must be completed later: microtasks. As the name implies, a microtask is a short-running function that does something significantly small, such as updating the state of variables or dispatching a new event. Dart VM provides a special queue for microtasks. The microtasks queue and the events queue process in a single event loop. However, the microtasks queue has higher priority than the events queue. An event loop processes all the microtasks at once, until the queue becomes empty. Then, it moves on to the events queue and processes one event per loop. Using the long-running code in the microtasks queue increases the risk of starving an event queue and can result in the reduction of responsiveness of an application.

Note

Make sure that the microtasks are extremely small to prevent blocking of the event loop.

Dart VM doesn't expose the event loop and we can't change or manage it. Bear in mind that the sequence of execution of events is predetermined by the events queue. You should also take into account the fact that the time at which the next event will be processed by the event loop is entirely unknown to you.

Synchronous versus Asynchronous code

There is a lot of speculation regarding what is better: synchronous or asynchronous programming. These conversations always end up in the architecture design. So, the important question is what is the difference between synchrony and asynchrony in code designs?

Let's discuss the terms that we will use. Operations are executed serially in the synchronous (sync) code; no more, no less. This is very popular because it is simple. The logical flow of the sync code is clear, and we can read and understand it without any significant effort. Let's take a look at the following code snippet:

import 'dart:io';

main() {
  try {
    File file = new File("data.txt");
    RandomAccessFile handler = file.openSync();
    List<int> content = handler.readSync(handler.lengthSync());
    String contentAsString = new String.fromCharCodes(content);
    print("Content:  $contentAsString");
    handler.closeSync();
  } on FileSystemException catch(e) {
    print(e.message);
  }
}

First, we create a file reference to data.txt on the filesystem. Then, we create a handler by opening file. Next, the handler reads the bytes from the file into a content variable. Finally, we translate the content to a string, print the result, and close the handler file. Some operations in this code take more time than others. The file-read operation can be quick because the size of the file is small. If it is bigger, then while reading from the file, our program will wait until it is done. It can take time to translate the content of the file. These operations block the execution of our program; each time-consuming operation has to finish before starting another one. This code is implemented in a sync manner and can be useful while doing simple tasks like this one. However, this approach cannot be applied in complex software. The complex program may have different pieces of code communicating with each other to draw a User Interface (UI), process keyboard input, read information from remote sites, or save information into the files at the same time. So, it's time to discuss the code written in an asynchronous (async) fashion. Async code does not wait for each operation to complete; the result of each operation will be handled later when available. Async code uses several important classes from Dart SDK and one of them is Future.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.93.12