Source code

Deep understanding of Flutter multithreading

This article belongs to the book "Jane book Liu Xiaozhuang".

< Jane book > Liu Xiaozhuang > https://www.jianshu.com/p/54da18ed1a9e


Flutter default is single thread task processing. If the new thread is not opened, the task is processed in the main thread by default.

Event queue

Similar to iOS applications, there are also concepts of event loops and message queues in Dart threads, but in Dart, threads are called isolate. After the application starts, execute the main function and run main isolate.

Each isolate contains an event loop and two event queues, event loop event loops, and event queue and microtask queue event queues. Event and microtask queues are similar to iOS source0 and source0.

  • Event queue: responsible for handling I/O events, drawing events, gesture events, receiving other isolate messages and other external events.

  • Microtask queue: you can add events to isolate internally, and the priority of events is higher than that of event queue.

 Image.png

Event queue

These two queues also have priority. When isolate starts executing, it will deal with the microtask event first. When there is no event in the microtask queue, the events in the event queue will be processed and executed repeatedly in this order. However, it should be noted that when executing the microtask event, the event execution of the event queue will be blocked, which will lead to event event response delay such as rendering, gesture response and so on. To ensure rendering and gesture response, time consuming operations should be placed in the event queue as far as possible.

Async, await

In asynchronous calls, there are three keywords, async, await, and Future, where async and await need to be used together. In Dart, async and await can be operated asynchronously. Async means that an asynchronous operation can be opened or a result of Future can be returned. If there is no return value, a Future with a return value of null is returned by default.

Async and await are essentially syntactic sugars of Dart to asynchronous operations, which can reduce nested calls of asynchronous calls and return to Future by async. The outside world can be called by chain calls. This syntax is introduced in the ES7 standard of JS, and the design of Dart is the same as that of JS.

The following is an asynchronous operation of a network request, and the Future of the Response type after the request is returned to the outside world. The external request can be called by await, and the return data can be obtained. You can see from the code that even if you return a string directly, Dart will wrap it up and become a Future.

 Future DataReqeust () async {
String requestURL ='https://jsonplaceholder.typicode.com/posts'; 
Client client = Client (); 
Future Response = client.get (requestURL); 
return response; 
}

Future LoadData () async {
Response response = await dataReqeust (); 
return response.body;}

In the code example, when executed to the loadData method, it will synchronize into the internal execution of the method. When executing to await, it will stop the internal execution of async and continue to execute the outer code. When await returns, it will continue to execute from the location of await. So the operation of await will not affect the execution of the code behind.

Here is an example of code that opens an asynchronous operation through async and waits for execution of requests or other operations through await, and receives the return value. When the data changes, the setState method is called and the data source is updated. Flutter updates the corresponding Widget node view.

 Class _SampleAppPageState extends State {
List widgets = []; 

@override
void initState () {
super.initState (); 
loadData (); 
}

loadData () async {
String dataURL = "https://jsonplaceholder.typicode.com/posts"; {
String = = "("); "(()) = (());"

Future

Future is a package of delay operations, which encapsulates asynchronous tasks into Future objects. After getting the Future object, the easiest way is to modify it with await, and wait for the result to continue. As mentioned in async and await above, await modification needs to be used in conjunction with async.

In Dart, time related operations are basically related to Future, such as delay operation, asynchronous operation and so on. The following is a very simple delay operation, implemented by delayed method of Future.

 LoadData () {DateTime.now / / DateTime.now (), get the current time 
DateTime now = DateTime.now (); 
print ('request begin $now'); 
Future.delayed (Duration (Duration 1), () = = (());

Dart also supports chain calls to Future, which is implemented by appending one or more then methods. This feature is very practical. For example, after a delay operation is completed, the then method will be invoked and a parameter can be passed to then. The way of calling is chain calling, which means that many layers can be processed. This is a bit like the RAC framework of iOS, and chain calls are used for signal processing.

 Future.delayed (Duration (seconds: 1), () {
int age = 18; 
return age;}.Then ((onValue) {
onValue++; 
print (onValue);});

Association

If you want to understand the principles of async and await, you must first understand the concept of association. Async and await are essentially syntactic sugar. The association, also known as coroutine, is a smaller unit than the thread. If it is from unit size, it can basically be understood as process > thread > Association.

task scheduling

Before we can understand the association, we first need to understand the concept of concurrent and concurrent. Concurrent means that the system manages multiple IO handover and is processed by CPU. Parallel refers to multi-core CPU executing multiple tasks at the same time.

Concurrent implementation is accomplished by non blocking operation + event notification, and event notification is also called "interruption". The operation process can be divided into two kinds. One is the operation of CPU to IO. After the completion of the operation, the interrupt is initiated and the IO operation is completed. The other is the IO initiates the interrupt, tells CPU to be able to carry on the operation.

Threads are also essentially dependent on interruption for scheduling. There is also a thread called blocking interruption, which means blocking threads when performing IO operations, and waiting for execution to continue. However, the consumption of threads is very large, and is not suitable for handling a large number of concurrent operations. Concurrent operations can be carried out through single thread concurrency. When multi-core CPU appears, single thread can not make good use of the advantage of multi-core CPU, so the concept of thread pool is introduced, and a large number of threads are managed through thread pool.

Association

In the execution of the program, there are two ways to leave the current calling location, continue to call other functions and return to return to leave the current function. However, when executing return, the local variables, parameters and other states of the current function in the call stack will be destroyed.

The association is divided into wireless coopera tion and wired Association. The wireless coopera will leave the current variable location in the heap area when it leaves the current call location. When it returns to the current location again, it will continue to get the variables from the heap area. Therefore, when the current function is executed, the variables are allocated directly to the heap area, and async and await belong to the wireless coopera. The wired Association will keep the variable in the stack area, and will continue to retrieve the call from the stack when it returns to the left position where the pointer is pointing.

Principles of async and await

Take async and await as examples, when the cooperations are executed, the execution to async means entering a coopera and executing the block of async synchronously. Async's code block is essentially equivalent to a function and has its own context. When executed to await, it means that there is a task to wait, and CPU to schedule other IO, that is, the code behind or other cooperating code. After a while, CPU will train once to see if a task has been processed, and the result can be carried on. If it can be executed, it will continue to follow the direction of the pointer when it left last time, that is, the location of the await flag.

Since no new thread has been opened, only IO interruption can change the CPU scheduling, so network requests such asynchronous operations can use async or await, but if a large number of time consuming synchronous operations are executed, it should use isolate to open up new threads to perform.

If we compare the dispatch_async with the iOS, we can see that the two are more similar. From the definition of structure, the association needs to store the relevant variables of the code blocks of the current await, and dispatch_async can also realize the storage capacity of temporary variables through block.

I was wondering a question before. Why does apple not introduce the characteristics of the association? Later I thought about it. Await and dispatch_async can be simply understood as asynchronous operations. The thread of OC is implemented based on Runloop. Dart is essentially an event loop, and the two have their own event queue, but the number and classification of queues are different.

I feel that when I'm running to await, I save the current context and mark the current position as a task to be processed, point to the current location with a pointer, and place the pending task in the queue of the current isolate. Ask for this task in every cycle of the event, and if it needs to be processed, the context will be processed.

Promise

Here I want to mention the Promise syntax in JS. There will be many if judgments or other nested calls in iOS, and Promise can change the horizontal nested calls to vertical chain calls. If you can introduce Promise into OC, you can make the code look more concise and intuitive.

Isolate

Isolate is the implementation of Dart platform for threads, but unlike ordinary Thread, isolate has independent memory, and isolate is composed of threads and independent memory. It is precisely because the memory between isolate threads is not shared, so there is no problem of resource looting between isolate threads, so there is no need to lock.

Through isolate, we can make good use of multi-core CPU to deal with a large number of time-consuming tasks. The communication between isolate threads is mainly carried out through port, and the process of port message passing is asynchronous. It can be seen from the Dart source code that the process of instantiating a isolate includes instantiation of isolate structure, allocation of thread memory in the heap, and configuration of port.

Isolate looks similar to the process. Before consulting Ali architects, Zong also said, "isolate's overall model is more like my own process, while async and await are more like threads." If you compare the definition of isolate and process, you will find that isolate is very much like a process.

Code example

Here is an example of isolate, which creates a new isolate in the example, and binds a method to handle the network request and data parsing, and returns the processed data back to the caller through port.

 The new method is to get the new monitor, to get the new monitor, and to use the new method to monitor the data, and then to call the user to identify the method of the user. LoadData () async {/ / through spawn creates a new isolate, and binds the static method 
ReceivePort receivePort =ReceivePort (); 
await Isolate.spawn (dataLoader). Create your own monitor port and send messages to the new isolate 
Future sendReceive (SendPort sendPort, String URL) {
ReceivePort receivePort =ReceivePort (); (E);

The threads in isolate and iOS are not quite the same, and the threads of isolate are more partial to the bottom. When a isolate is generated, its memory is independent and can not be accessed from one another. However, isolate provides a port based message mechanism to communicate messages between each other by establishing sendPort and receiveport on both sides of the communication, which is called message passing in Dart.

From the above example, we can see that in the process of isolate message passing, port transmission is essentially. Pass port to other isolate, other isolate get sendPort through port, send message to the caller to carry on the mutual message transmission.

Embedder

As its name is, Embedder is an embedded layer that embeds Flutter on various platforms. Embedder is responsible for the original platform plug-ins, thread management, event loops, and so on.

 Image.png

Flutter System Overriew

There are four Runner in Embedder and four Runner respectively. Each Flutter Engine corresponds to a UI Runner, GPU Runner, IO Runner, but all Engine share a Platform Runner.

 Image.png

Embedder

Runner and isolate are not independent, they are independent of each other. Take the iOS platform as an example, the implementation of Runner is CFRunLoop, which continues to process tasks in a way of event cycling. And Runner not only handles Engine tasks, but also the task of Native Plugin's native platform. Isolate is managed by Dart VM and has nothing to do with native platform threads.

Platform Runner

Main Thread is very similar to Platform Runner and iOS platform. In Flutter, all tasks should be placed in Platform except time consuming operation. Many API in Flutter is not thread safe. Placing it in other threads may lead to some bug.

But time consuming operations such as IO should be done in other threads, otherwise it will affect the normal execution of Platform, or even be destroyed by watchdog. But it should be noted that due to the mechanism of Embedder Runner, Platform is blocked and will not cause page carton.

Not only is the code of Flutter Engine executed in Platform, but the task of Native Plugin is also distributed to Platform. In fact, the code on the native side runs in Platform Runner, while the Flutter side code runs in Root Isolate. If the time-consuming code is executed in Platform, the main thread of the native platform will be stuck.

UI Runner

UI Runner is responsible for executing Root Isolate code for Flutter Engine, in addition to handling Native Plugin tasks. Root Isolate has many functional methods bound to handle its own events. When the program starts, Flutter Engine will bind the UI Runner processing function for Root, enabling Root Isolate to have the ability to submit rendering frames.

When Root Isolate submits a rendering frame to Engine, Engine will wait for the next Vsync. When the next Vsync comes, Root Isolate will arrange the layout of Widgets, and generate the description of the display information of the page, and send the information to Engine.

Since layout is performed on widgets and layer tree is generated by UI Runner, if a large amount of time consuming processing is done in UI Runner, it will affect the display of pages, so the time-consuming operation should be given to other isolate processing, such as events from Native Plugin.

 Image.png

Rendering Pipeline.jpg

GPU Runner

GPU Runner is not directly responsible for rendering operations, it is responsible for GPU related management and scheduling. When layer tree information arrives, GPU Runner submits it to the specified rendering platform. The rendering platform is Skia configured, and different platforms may have different implementations.

GPU Runner is relatively independent, other than Embedder, other threads can not submit rendering information to them.

 Image.png

Graphics Pipeline

IO Runner

Some of the time-consuming operations in GPU Runner are handled in IO Runner, such as picture reading, decompression, rendering and so on. However, only GPU Runner can submit rendering information to GPU. In order to ensure that IO Runner also has this capability, IO Runner will refer to context of GPU Runner, so that it has the ability to submit rendering information to GPU.

Fabulous ( Zero )

This article is composed of Contributor Creation, article address: Https://blog.isoyu.com/archives/shenrulijieflutterduoxiancheng.html
Use Knowledge sharing signature 4 The international license agreement is licensed. In addition to the reprint / provenance, all originals or translations of this site must be signed before retransmission. The final editing time is May, 22, 2019 at 05:04 afternoon.

Hot review article

Comment

[required]

Invisibility?

Please wait three seconds after submission, so as not to cause unsuccessful successes and duplication.