In-depth understanding of Flutter multithreading

该文章属于<简书 — 刘小壮>原创,转载请注明:

< Jianshu-Liu Xiaozhuang > https://www.jianshu.com/p/54da18ed1a9e


封面图

FlutterThe default is single-threaded task processing. If no new thread is opened, the task is processed in the main thread by default.

event queue

Much like iOS applications, inDartThe concepts of event loop and message queue also exist in the thread ofDartThe middle thread is calledisolate. After the application is started, execution startsmainFunction and runmain isolate.

Each ..isolateComprises an event loop and two event queues,event loopEvent cycle, andevent queueAndmicrotask queueEvent queue,eventAndmicrotaskThe queue is a bit like iOSsource0Andsource1.

  • Event queue: responsible for handling I/O events, drawing events, gesture events, receiving otherisolateNews and other external events.
  • Microtask queue: you can go toisolateInternal addition of events, priority ratio of eventsevent queueGao.

事件队列

The two queues also have priority whenisolateAfter the execution starts, it will be processed firstmicrotaskThe incident, whenmicrotaskOnly when there are no events in the queue will they be processedeventEvents in the queue are repeated in this order. However, it should be noted that when executingmicrotaskEvent, it will blockeventQueue event execution, which will result in rendering, gesture response, etc.eventDelay of event response. In order to ensure rendering and gesture response, time-consuming operations should be put as much as possible ineventIn the queue.

async、await

There are three keywords in the asynchronous call.asyncawaitFutureOf whichasyncAndawaitIt needs to be used together. InDartCan be passed throughasyncAndawaitPerforming asynchronous operation,asyncIndicates to open an asynchronous operation or return anFutureResults If there is no return value, the default return value isnullTheFuture.

asyncawaitIn essence, it isDartFor a syntax sugar of asynchronous operation, nested calls of asynchronous calls can be reduced, and the number of nested calls can be reduced byasyncA is returned after modificationFuture, the outside world can call in the form of a chain call. This grammar isJSTheES7According to the standard,DartThe design andJSSame.

The following encapsulates an asynchronous operation requested by a network, and the requestedResponseOf typeFutureReturn to the outside world, the outside world can throughawaitCall this request and get the returned data. As you can see from the code, even if you return a string directly,DartIt will also be packaged and become oneFuture.

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

Future<String> loadData() async {
    Response response = await dataReqeust();
    return response.body;
}

In the code example, execute toloadDataWhen the method is executed, it synchronously enters the inside of the method for execution. when the method is executed toawaitIt will stop whenasyncInternal execution to continue executing external code. WhenawaitAfter returning, will continue fromawaitThe location of the continued execution. soawaitThe operation of the, will not affect the execution of the following code.

The following is a code example throughasyncStart an asynchronous operation byawaitWait for the execution of a request or other operation and receive the return value. Called when data changessetStateMethod and update the data source,FlutterThe corresponding will be updatedWidgetNode view.

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

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

  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });
  }
}

Future

FutureIs a package of delay operations, asynchronous tasks can be packaged asFutureObject. GetFutureObject, the simplest method is to use theawaitModifies and waits for the returned result to continue to execute downward. As aboveasync、awaitAs mentioned in, useawaitThe decoration needs coordination.asyncTogether.

InDartTime-related operations are basically the sameFutureRelevant, such as delay operation, asynchronous operation, etc. The following is a very simple delay operation, throughFutureThedelayedMethod is implemented.

loadData() {
    // DateTime.now(),获取当前时间
    DateTime now = DateTime.now();
    print('request begin $now');
    Future.delayed(Duration(seconds: 1), (){
      now = DateTime.now();
      print('request response $now');
    });
}

DartAlso supports the rightFutureBy appending one or morethenMethod, this feature is very practical. For example, after a delay operation is completed, thethenMethod and can pass a parameter to thethen. The calling method is chain calling, which means that many layers of processing can be carried out. This is a bit like iOSRACFrame, chain call for signal processing.

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

coroutine

If you want to knowasyncawaitThe principle of, we must first understand the concept of association process,asyncawaitIn essence, it is a grammatical sugar of Xiecheng. Xiecheng, also known ascoroutineIs a unit smaller than a thread. In terms of unit size, it can be basically understood as process-> thread-> coordination.

task scheduling

Before understanding the coordination process, we must first understand the concepts of concurrency and parallelism. What is outrageous is that the system manages the switching of multiple IO’s and turns it over to the CPU for processing. Parallel refers to multi-core CPU executing multiple tasks at the same time.

Concurrent implementation is accomplished by non-blocking operation+event notification, which is also called “interrupt.” The operation process is divided into two types, one is that the CPU operates the IO and initiates an interrupt to tell the IO that the operation is completed after the operation is completed. The other is IO initiated interrupt to tell CPU to operate.

In essence, threads also rely on interrupts for scheduling. Another type of thread is called “blocking interrupt”, which blocks threads when IO operations are performed and waits for execution to complete before continuing execution. However, the consumption of threads is very large, which is not suitable for the processing of a large number of concurrent operations, and a large number of concurrent operations can be performed through single-thread concurrency. When multi-core CPU appears, a single thread cannot make good use of the advantages of multi-core CPU, so the concept of thread pool is introduced to manage a large number of threads through thread pool.

coroutine

In the process of program execution, there are two ways to leave the current calling position and continue calling other functions andreturnReturns away from the current function. But executionreturnThe state of local variables and formal parameters of the current function in the call stack will be destroyed.

The coordination process is divided into wireless coordination process and wired coordination process. When the wireless coordination process leaves the current calling position, it will place the current variable in the heap area, and when it returns to the current position again, it will continue to obtain the variable from the heap area. Therefore, variables are normally allocated directly to the heap when the current function is executed, whereasasyncawaitIt belongs to one kind of wireless coordination. Wired coroutine will keep the variable in the stack area, and will continue to fetch the call from the stack when returning to the exit position pointed by the pointer.

Async, await principle

In order toasyncawaitFor example, when the coordination process is executed, it is executed toasyncIt means entering a coordination process and will be executed synchronously.asyncThe code block of.asyncThe code block of is essentially equivalent to a function and has its own context. When executed toawaitWhen, it indicates that there are tasks to wait for, and the CPU schedules and executes other IO, that is, the following code or other coordination code. After a period of time, the CPU will rotate once to see if a certain coordination process has been completed and the returned results can be continued. if the execution can be continued, the execution will continue along the position pointed by the pointer when the CPU left last time, that isawaitThe location of the sign.

Since no new thread is started, only IO interrupt is performed to change CPU scheduling, asynchronous operation such as network request can be usedasyncawaitHowever, if you are performing a large number of time-consuming synchronization operations, you should use theisolateOpen up a new thread to execute.

If you use Concorde and iOSdispatch_asyncBy comparison, we can find that the two are quite similar. Judging from the structural definition, the coordination process needs to integrate the currentawaitThe variables related to the code blocks of the are stored,dispatch_asyncCan also passblockTo realize the storage capacity of temporary variables.

I was thinking about a question before. Why didn’t Apple introduce the feature of synergetics? After thinking about it for a moment,awaitAnddispatch_asyncCan be simply understood as asynchronous operation, OC thread is based onRunloopIn fact,DartIn essence, there is also an event cycle, and both have their own event queues, but the number and classification of queues are different.

I think when it comes to implementationawaitWhen, the current context is saved, the current position is marked as a task to be processed, a pointer is used to point to the current position, and the task to be processed is put into the currentisolateIn the queue of the. Ask about this task during each event cycle. If processing is needed, resume the context for task processing.

Promise

I would like to mention hereJSFrom insidePromiseGrammar, there will be a lot in iOSifJudgment or other nested calls, andPromiseYou can change the previous horizontal nested call to a vertical chain call. If you can putPromiseIntroduction to OC can make the code look more concise and intuitive.

isolate

isolateYesDartThe implementation scheme of the platform for threads is commonThreadThe difference is,isolateWith independent memory,isolateConsists of threads and independent memory. It is because ofisolateMemory is not shared between threads, soisolateThere is no problem of resource grabbing between threads, so there is no need for locks.

viaisolateThe multi-core CPU can be well utilized to process a large number of time-consuming tasks.isolateCommunication between threads is mainly throughportCome on, this oneportThe process of message delivery is asynchronous. viaDartSource code can also be seen, instantiating aisolateThe process of includes instantiationisolateStructure, allocating thread memory in heap, configuringportWait for the process.

isolateIt seems to be similar to the process. when I asked architect Ali about zongxin, zongxin also saidisolateThe overall model of my own understanding is actually more like a process, whileasyncawaitMore like threads. ” If you compareisolateAnd the definition of the process, you will find that indeedisolateMuch like a process.

Code sample

Here is oneisolateIn this case, a new one was created.isolateAnd bind a method to process network request and data analysis, and pass throughportReturn the processed data to the caller.

loadData() async {
    // 通过spawn新建一个isolate,并绑定静态方法
    ReceivePort receivePort =ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
    
    // 获取新isolate的监听port
    SendPort sendPort = await receivePort.first;
    // 调用sendReceive自定义方法
    List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
    print('dataList $dataList');
}

// isolate的绑定方法
static dataLoader(SendPort sendPort) async{
    // 创建监听port,并将sendPort传给外界用来调用
    ReceivePort receivePort =ReceivePort();
    sendPort.send(receivePort.sendPort);
    
    // 监听外界调用
    await for (var msg in receivePort) {
      String requestURL =msg[0];
      SendPort callbackPort =msg[1];
    
      Client client = Client();
      Response response = await client.get(requestURL);
      List dataList = json.decode(response.body);
      // 回调返回值给调用者
      callbackPort.send(dataList);
    }    
}

// 创建自己的监听port,并且向新isolate发送消息
Future sendReceive(SendPort sendPort, String url) {
    ReceivePort receivePort =ReceivePort();
    sendPort.send([url, receivePort.sendPort]);
    // 接收到返回值,返回给调用者
    return receivePort.first;
}

isolateUnlike threads in iOS,isolateThe thread of the is lower. When generating aisolateAfter that, their memories are independent of each other and cannot be accessed. But ..isolateBased onportBy establishing a message mechanism for both sides of the communicationsendPortAndreceiveport, for mutual messaging, inDartIt is called messaging in.

As can be seen from the above example, in progressisolateIn essence, the process of message transmission is to carry outportThe transfer of. willportPass on to othersisolate, othersisolateviaportGetsendPortTo send messages to callers for mutual messaging.

Embedder

As its name suggests,EmbedderIs an embedded layer that willFlutterEmbedded on each platform.EmbedderThe scope of responsibility includes native platform plug-ins, thread management, event loops, etc.

Flutter System Overriew

EmbedderThere are four inRunnerFourRunnerThey are as follows. Each of themFlutter EngineOne for eachUI RunnerGPU RunnerIO RunnerBut allEngineShare onePlatform Runner.

Embedder

RunnerAndisolateIt is not the same thing. They are independent of each other. Take iOS platform as an example.RunnerThe realization of isCFRunLoopTo continuously process tasks in a loop of events. AndRunnerNot only processingEngineThe task, andNative PluginThe mission of the native platform. AndisolateThen byDart VMManagement has nothing to do with native platform threads.

Platform Runner

Platform RunnerAnd iOS platformMain ThreadVery similar, inFlutterExcept for time-consuming operations, all tasks should be placed inPlatformChina,FlutterMany apis in are not thread-safe, and placing them in other threads may cause some bugs.

However, time-consuming operations such as IO should be completed in other threads, otherwise it will affectPlatformThe normal execution of, or even bewatchdogKill. However, it should be noted that due toEmbedder RunnerThe mechanism of,PlatformBlocked pages will not cause page sticking.

Not onlyFlutter EngineThe code for is inPlatformTo be implemented in,Native PluginThe task will also be distributed toPlatformTo be implemented in. In fact, the code on the native side runs on thePlatform RunnerChina, whileFlutterThe code on the side runs on theRoot IsolateIn, if inPlatformIf time-consuming code is executed in, the main thread of the native platform will be blocked.

UI Runner

UI RunnerResponsible forFlutter Enginecarry outRoot IsolateThe code for the, in addition to, also handles data from theNative PluginThe task of.Root IsolateIn order to handle its own events, many function methods are bound. When the program starts,Flutter EngineWill beRootBindingUI RunnerThe processing function of the makesRoot IsolateAbility to submit rendered frames.

WhenRoot IsolateToEngineWhen submitting a rendered frame,EngineWill wait for the next vsync, when the next vsync arrives, byRoot IsolateYesWidgetsPerforming layout operation, generating a description of the display information of the page, and giving the information toEngineTo deal with.

Because of the rightwidgetscarry throughlayoutAnd generatelayer treeYesUI RunnerIf it is carried out inUI RunnerA lot of time-consuming processing will affect the display of the page, so time-consuming operations should be handed over to others.isolateProcessing, e.g. fromNative PluginThe incident.

Rendering Pipeline

GPU Runner

GPU RunnerIt is not directly responsible for rendering operations, but is responsible for GPU-related management and scheduling. Whenlayer treeWhen the information arrives,GPU RunnerSubmit it to the specified rendering platform. The rendering platform is configured by Skia. Different platforms may have different implementations.

GPU RunnerRelatively independent, exceptEmbedderNo other thread can submit rendering information to it.

Graphics Pipeline

IO Runner

SomeGPU RunnerIn the more time-consuming operation, put inIO RunnerFor example, picture reading, decompression, rendering and other operations. But onlyGPU RunnerTo submit rendering information to GPU, in order to ensureIO RunnerAlso have this ability, soIO RunnerWill quoteGPU RunnerThecontext, thus having the ability to submit rendering information to GPU.


Because of typesetting problems, the reading experience of simple books is not good, and there are many problems such as layout, picture display, code, etc. So it is suggested to meGithubOn, downloadFlutter programming guide PDFA collection. Take allFlutterThere are a total of three articles, all written in thisPDFIn addition, there is a directory on the left for easy reading.

Flutter编程指南

Download address:Flutter programming guide PDF
Please give your bosses a compliment, thank you!