Flutter的Event Loop、Future及Isolate

轉載請聯繫: 微信號: michaelzhoujay數組

原文請訪問個人博客微信


本文主要介紹Flutter中Event Loop以及如何在Flutter中作parallel processing.網絡

Event Loop

First things first, everyone needs to bear in mind that Dart is Single Thread and Flutter relies on Dart.async

IMPORTANT Dart executes one operation at a time, one after the other meaning that as long as one operation is executing, it cannot be interrupted by any other Dart code.ide

跟AndroidVM相似,當你啓動一個Flutter的App,那麼就會系統就會啓動一個DartVM Flutter中的Dart VM啓動後,那麼一個新的Thread就會被建立,而且只會有一個線程,它運行在本身的Isolate中。oop

當這個Thread被建立後,DartVM會自動作如下3件事情:學習

  • 初始化2個隊列,一個叫「MicroTask」,一個叫「Event」,都是FIFO隊列
  • 執行 main() 方法,一旦執行完畢就作下一步
  • 啓動 Event Loop

Event Loop就像一個 infinite loop,被內部時鐘來調諧,每個tick,若是沒有其餘Dart Code在執行,就會作以下的事情(僞代碼):fetch

void eventLoop(){
    while (microTaskQueue.isNotEmpty){
        fetchFirstMicroTaskFromQueue();
        executeThisMicroTask();
        return;
    }

    if (eventQueue.isNotEmpty){
        fetchFirstEventFromQueue();
        executeThisEventRelatedCode();
    }
}
複製代碼

MicroTask Queue

MicroTask Queue是爲了很是短暫的asynchronously的內部操做來設計的。在其餘Dart代碼運行完畢後,且在移交給Event Queue前。ui

舉個例子,咱們常常須要在close一個resource之後,dispose掉一些handle,下面的這個例子裏,scheduleMicroTask 能夠用來作 dispose 的事情:this

MyResource myResource;

    ...

    void closeAndRelease() {
        scheduleMicroTask(_dispose);
        _close();
    }

    void _close(){
        // The code to be run synchronously
        // to close the resource
        ...
    }

    void _dispose(){
        // The code which has to be run
        // right after the _close()
        // has completed
    }
複製代碼

這裏,雖然scheduleMicroTask(_dispose)語句在_close()語句以前,可是因爲上面說到的,「其餘Dart代碼執行完畢後」,因此_close()會先執行,而後執行 Event loop 的 microTask。

即便你已經知道 microTask 的執行時機,並且還學習了用scheduleMicroTask來使用 microTask,可是 microTask 也不是你經常使用的東西。就 Flutter 自己來講,整個 source code只引用了 scheduleMicroTask() 7次。

Event Queue

Event Queue 主要用來處理當某些事件發生後,調用哪些操做,這些事件分爲:

  • 外部事件:
    • I/O
    • gesture
    • drawing
    • timers
    • streams
  • futures

事實上,每當外部事件發生時,要執行的代碼都是在 Event Queue裏找到的。 只要當沒有 MicroTask 須要run了,那麼 Event Queue 就會從第一個事件開始處理

Futures

當你建立了一個future的實例,其實是作了如下幾件事情:

  • 一個 instance 被建立,放到一個內部的數組後從新排序,由dart管理
  • 須要後續被執行的代碼,被直接push到 Event Queue裏面
  • future當即同步返回一個狀態incomplete
  • 若是有其餘 synchronous 代碼,會先執行這些 sychronous 代碼

Future和其餘Event同樣,會在EventQueue裏被執行。 如下的例子用來講明Future和上面的Event執行過程同樣

void main(){
    print('Before the Future');
    Future((){
        print('Running the Future');
    }).then((_){
        print('Future is complete');
    });
    print('After the Future');
}
複製代碼

執行後,會獲得以下輸出:

Before the Future
After the Future
Running the Future
Future is complete
複製代碼

咱們來分步驟解釋一下代碼是如何執行的:

  1. 執行print(‘Before the Future’)
  2. 將function 「(){print(‘Running the Future’);}」 添加到 event queue
  3. 執行print(‘After the Future’)
  4. Event Loop 取到第2步裏說的代碼,而且運行
  5. 代碼執行完畢後,它嘗試找到 then 語句並運行

A Future is NOT executed in parallel but following the regular sequence of events, handled by the Event Loop

Async Methods

若是在任何一個方法的聲明部分加上 async 後綴,那麼你實際上在向dart代表:

  • 該方法的結果是一個 future
  • 若是調用時遇到一個await,那麼它會同步執行,會把它所在的代碼上下文給pause住
  • 下一行代碼會等待,直到上面的future(被await等的)結束

Isolate

每一個線程有本身的Isolate,你能夠用Isolate.spawn 或者是 compute 來建立一個 Isolate 每一個Isolate都有本身的Data,和Event loop Isolate之間經過消息來進行溝通

Isolate.spawn(
  aFunctionToRun,
  {'data' : 'Here is some data.'}, 
);
複製代碼
compute(
  (paramas) {
    /* do something */
  },
  {'data' : 'Here is some data.'},
);  
複製代碼

當你在一個Isolate中,建立了一個新的Isolate,而後要和新的Isolate進行溝通,那麼就須要SendPortReceivePort。 爲了能溝通,兩個Isolate必需要互相知曉對方的port:

  • 本地 Isolate 經過 SendPort 來收/發消息,官方起的名字真的是有些讓人困惑。
  • 當你建立一個Isolate時,就須要給 spawn 方法傳遞一個 ReceivePort 的實例,後續會用這個port來收/發消息,同時也會經過這個port把本地 Isolate的sendport返回

找了一個例子,感覺一下

//
// The port of the new isolate
// this port will be used to further
// send messages to that isolate
//
SendPort newIsolateSendPort;

//
// Instance of the new Isolate
//
Isolate newIsolate;

//
// Method that launches a new isolate
// and proceeds with the initial
// hand-shaking
//
void callerCreateIsolate() async {
    //
    // Local and temporary ReceivePort to retrieve
    // the new isolate's SendPort
    //
    ReceivePort receivePort = ReceivePort();

    //
    // Instantiate the new isolate
    //
    newIsolate = await Isolate.spawn(
        callbackFunction,
        receivePort.sendPort,
    );

    //
    // Retrieve the port to be used for further
    // communication
    //
    newIsolateSendPort = await receivePort.first;
}

//
// The entry point of the new isolate
//
static void callbackFunction(SendPort callerSendPort){
    //
    // Instantiate a SendPort to receive message
    // from the caller
    //
    ReceivePort newIsolateReceivePort = ReceivePort();

    //
    // Provide the caller with the reference of THIS isolate's SendPort
    //
    callerSendPort.send(newIsolateReceivePort.sendPort);

    //
    // Further processing
    //
}
複製代碼

兩個 Isolate 都有了各自的port,那麼它們就能夠開始互發消息了:

本地Isolate向新Isolate發消息並回收結果:

Future<String> sendReceive(String messageToBeSent) async {
    //
    // We create a temporary port to receive the answer
    //
    ReceivePort port = ReceivePort();

    //
    // We send the message to the Isolate, and also
    // tell the isolate which port to use to provide
    // any answer
    //
    newIsolateSendPort.send(
        CrossIsolatesMessage<String>(
            sender: port.sendPort,
            message: messageToBeSent,
        )
    );

    //
    // Wait for the answer and return it
    //
    return port.first;
}
複製代碼

本地Isolate被動收消息,還記得上面的spawn方法嗎,第一個參數是 callbackFunction這個方法就是用來收結果的:

//
// Extension of the callback function to process incoming messages
//
static void callbackFunction(SendPort callerSendPort){
    //
    // Instantiate a SendPort to receive message
    // from the caller
    //
    ReceivePort newIsolateReceivePort = ReceivePort();

    //
    // Provide the caller with the reference of THIS isolate's SendPort
    //
    callerSendPort.send(newIsolateReceivePort.sendPort);

    //
    // Isolate main routine that listens to incoming messages,
    // processes it and provides an answer
    //
    newIsolateReceivePort.listen((dynamic message){
        CrossIsolatesMessage incomingMessage = message as CrossIsolatesMessage;

        //
        // Process the message
        //
        String newMessage = "complemented string " + incomingMessage.message;

        //
        // Sends the outcome of the processing
        //
        incomingMessage.sender.send(newMessage);
    });
}

//
// Helper class
//
class CrossIsolatesMessage<T> {
    final SendPort sender;
    final T message;

    CrossIsolatesMessage({
        @required this.sender,
        this.message,
    });
}
複製代碼

Isolate 的銷燬

若是建立的Isolate再也不使用,那麼最好是能將其release掉:

void dispose(){
    newIsolate?.kill(priority: Isolate.immediate);
    newIsolate = null;
}
複製代碼

Single-Listener Streams

實際上Isolate之間的溝通是經過 「Single-Listener」 Streams 來實現的。

compute

上面說過建立 Isolate的方式,其中 compute 適合於建立之後執行任務,並且完成任務後你不但願有任何溝通。 compute是個function:

  • spawn 一個 Isolate
  • 運行一個callback,傳遞一些data,返回結果
  • 在callback執行完畢時,kill掉isolate

適合 Isolate 的一些場景

  • JSON 解析
  • encryption 加解密
  • 圖像處理,好比 cropping
  • 從網絡加載圖片

如何挑選呢?

通常來講:

  • 若是一個方法耗時幾十毫秒,用 Future
  • 若是一個操做須要幾百毫秒了,那麼就用 Isolate
相關文章
相關標籤/搜索