Flutter(五)之完全搞懂Dart異步

前言一:接下來一段時間我會陸續更新一些列Flutter文字教程前端

更新進度: 每週至少兩篇;vue

更新地點: 首發於公衆號,次日更新於掘金、思否等地方;面試

更多交流: 能夠添加個人微信 372623326,關注個人微博:coderwhy算法

但願你們能夠 幫忙轉發,點擊在看,給我更多的創做動力。編程

前言二:在寫這篇文章以前,我一直在猶豫,要不要在這裏講解Dart的異步相關話題,由於這部份內容很容易讓初學者望而卻步:json

一、關於單線程和異步之間的關係,比較容易讓人迷惑,雖然我必定會用本身的方式儘量讓你聽懂。數組

二、大量的異步操做方式(Future、await、async等),目前你看不到具體的應用場景。(好比你學習過前端中的Promise、await、async可能會比較簡單,可是我會假設你沒有這樣的基礎)。微信

不過,聽我說:若是這一個章節你學完以後還有不少疑惑,沒有關係,在後面用到相關知識時,回頭來看,你會豁然開朗。網絡

一. Dart的異步模型

咱們先來搞清楚Dart是如何搞定異步操做的數據結構

1.1. Dart是單線程的

1.1.1. 程序中的耗時操做

開發中的耗時操做:

  • 在開發中,咱們常常會遇到一些耗時的操做須要完成,好比網絡請求、文件讀取等等;
  • 若是咱們的主線程一直在等待這些耗時的操做完成,那麼就會進行阻塞,沒法響應其它事件,好比用戶的點擊;
  • 顯然,咱們不能這麼幹!!

如何處理耗時的操做呢?

  • 針對如何處理耗時的操做,不一樣的語言有不一樣的處理方式。
  • 處理方式一: 多線程,好比Java、C++,咱們廣泛的作法是開啓一個新的線程(Thread),在新的線程中完成這些異步的操做,再經過線程間通訊的方式,將拿到的數據傳遞給主線程。
  • 處理方式二: 單線程+事件循環,好比JavaScript、Dart都是基於單線程加事件循環來完成耗時操做的處理。不過單線程如何能進行耗時的操做呢?!

1.1.2. 單線程的異步操做

我以前碰到不少開發者都對單線程的異步操做充滿了問號???

單線程異步操做

其實它們並不衝突:

  • 由於咱們的一個應用程序大部分時間都是處於空閒的狀態的,並非無限制的在和用戶進行交互。
  • 好比等待用戶點擊、網絡請求數據的返回、文件讀寫的IO操做,這些等待的行爲並不會阻塞咱們的線程;
  • 這是由於相似於網絡請求、文件讀寫的IO,咱們均可以基於非阻塞調用;

阻塞式調用和非阻塞式調用

若是想搞懂這個點,咱們須要知道操做系統中的阻塞式調用非阻塞式調用的概念。

  • 阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態。
  • 阻塞式調用: 調用結果返回以前,當前線程會被掛起,調用線程只有在獲得調用結果以後纔會繼續執行。
  • 非阻塞式調用: 調用執行以後,當前線程不會中止執行,只須要過一段時間來檢查一下有沒有結果返回便可。

咱們用一個生活中的例子來模擬:

  • 你中午餓了,須要點一份外賣,點外賣的動做就是咱們的調用,拿到最後點的外賣就是咱們要等待的結果。
  • 阻塞式調用: 點了外賣,再也不作任何事情,就是在傻傻的等待,你的線程中止了任何其餘的工做。
  • 非阻塞式調用: 點了外賣,繼續作其餘事情:繼續工做、打把遊戲,你的線程沒有繼續執行其餘事情,只須要偶爾去看一下有沒有人敲門,外賣有沒有送到便可。

而咱們開發中的不少耗時操做,均可以基於這樣的 非阻塞式調用

  • 好比網絡請求自己使用了Socket通訊,而Socket自己提供了select模型,能夠進行非阻塞方式的工做
  • 好比文件讀寫的IO操做,咱們可使用操做系統提供的基於事件的回調機制

這些操做都不會阻塞咱們單線程的繼續執行,咱們的線程在等待的過程當中能夠繼續去作別的事情:喝杯咖啡、打把遊戲,等真正有了響應,再去進行對應的處理便可。

這時,咱們可能有兩個問題:

  • 問題一: 若是在多核CPU中,單線程是否是就沒有充分利用CPU呢?這個問題,我會放在後面來說解。
  • 問題二: 單線程是如何來處理網絡通訊、IO操做它們返回的結果呢?答案就是事件循環(Event Loop)。

1.2. Dart事件循環

1.2.1. 什麼是事件循環

單線程模型中主要就是在維護着一個事件循環(Event Loop)。

事件循環是什麼呢?

  • 事實上事件循環並不複雜,它就是將須要處理的一系列事件(包括點擊事件、IO事件、網絡事件)放在一個事件隊列(Event Queue)中。
  • 不斷的從事件隊列(Event Queue)中取出事件,並執行其對應須要執行的代碼塊,直到事件隊列清空位置。

咱們來寫一個事件循環的僞代碼:

// 這裏我使用數組模擬隊列, 先進先出的原則
List eventQueue = []; 
var event;

// 事件循環從啓動的一刻,永遠在執行
while (true) {
  if (eventQueue.length > 0) {
    // 取出一個事件
    event = eventQueue.removeAt(0);
    // 執行該事件
    event();
  }
}
複製代碼

當咱們有一些事件時,好比點擊事件、IO事件、網絡事件時,它們就會被加入到eventLoop中,當發現事件隊列不爲空時發現,就會取出事件,而且執行。

  • 齒輪就是咱們的事件循環,它會從隊列中一次取出事件來執行。

img

1.2.2. 事件循環代碼模擬

這裏咱們來看一段僞代碼,理解點擊事件和網絡請求的事件是如何被執行的:

  • 這是一段Flutter代碼,不少東西你們可能不是特別理解,可是耐心閱讀你會讀懂咱們在作什麼。
  • 一個按鈕RaisedButton,當發生點擊時執行onPressed函數。
  • onPressed函數中,咱們發送了一個網絡請求,請求成功後會執行then中的回調函數。
RaisedButton(
  child: Text('Click me'),
  onPressed: () {
    final myFuture = http.get('https://example.com');
    myFuture.then((response) {
      if (response.statusCode == 200) {
        print('Success!');
      }
    });
  },
)
複製代碼

這些代碼是如何放在事件循環中執行呢?

  • 一、當用戶發生點擊的時候,onPressed回調函數被放入事件循環中執行,執行的過程當中發送了一個網絡請求。
  • 二、網絡請求發出去後,該事件循環不會被阻塞,而是發現要執行的onPressed函數已經結束,會將它丟棄掉。
  • 三、網絡請求成功後,會執行then中傳入的回調函數,這也是一個事件,該事件被放入到事件循環中執行,執行完畢後,事件循環將其丟棄。

儘管onPressed和then中的回調有一些差別,可是它們對於事件循環來講,都是告訴它:我有一段代碼須要執行,快點幫我完成。

二. Dart的異步操做

Dart中的異步操做主要使用Future以及async、await。

若是你以前有過前端的ES六、ES7編程經驗,那麼徹底能夠將Future理解成Promise,async、await和ES7中基本一致。

可是若是沒有前端開發經驗,Future以及async、await如何理解呢?

2.1. 認識Future

我思考了好久,這個Future到底應該如何講解

2.1.1. 同步的網絡請求

咱們先來看一個例子吧:

  • 在這個例子中,我使用getNetworkData來模擬了一個網絡請求;
  • 該網絡請求須要3秒鐘的時間,以後返回數據;
import "dart:io";

main(List<String> args) {
  print("main function start");
  print(getNetworkData());
  print("main function end");
}

String getNetworkData() {
  sleep(Duration(seconds: 3));
  return "network data";
}
複製代碼

這段代碼會運行怎麼的結果呢?

  • getNetworkData會阻塞main函數的執行
main function start
// 等待3秒
network data
main function end
複製代碼

顯然,上面的代碼不是咱們想要的執行效果,由於網絡請求阻塞了main函數,那麼意味着其後全部的代碼都沒法正常的繼續執行。

2.1.2. 異步的網絡請求

咱們來對咱們上面的代碼進行改進,代碼以下:

  • 和剛纔的代碼惟一的區別在於我使用了Future對象來將耗時的操做放在了其中傳入的函數中;
  • 稍後,咱們會講解它具體的一些API,咱們就暫時知道我建立了一個Future實例便可;
import "dart:io";

main(List<String> args) {
  print("main function start");
  print(getNetworkData());
  print("main function end");
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    return "network data";
  });
}
複製代碼

咱們來看一下代碼的運行結果:

  • 一、這一次的代碼順序執行,沒有出現任何的阻塞現象;
  • 二、和以前直接打印結果不一樣,此次咱們打印了一個Future實例;
  • 結論:咱們將一個耗時的操做隔離了起來,這個操做不會再影響咱們的主線程執行了。
  • 問題:咱們如何去拿到最終的結果呢?
main function start
Instance of 'Future<String>'
main function end
複製代碼

獲取Future獲得的結果

有了Future以後,如何去獲取請求到的結果:經過.then的回調:

main(List<String> args) {
  print("main function start");
  // 使用變量接收getNetworkData返回的future
  var future = getNetworkData();
  // 當future實例有返回結果時,會自動回調then中傳入的函數
  // 該函數會被放入到事件循環中,被執行
  future.then((value) {
    print(value);
  });
  print(future);
  print("main function end");
}
複製代碼

上面代碼的執行結果:

main function start
Instance of 'Future<String>'
main function end
// 3s後執行下面的代碼
network data
複製代碼

執行中出現異常

若是調用過程當中出現了異常,拿不到結果,如何獲取到異常的信息呢?

import "dart:io";

main(List<String> args) {
  print("main function start");
  var future = getNetworkData();
  future.then((value) {
    print(value);
  }).catchError((error) { // 捕獲出現異常時的狀況
    print(error);
  });
  print(future);
  print("main function end");
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    // 再也不返回結果,而是出現異常
    // return "network data";
    throw Exception("網絡請求出現錯誤");
  });
}

複製代碼

上面代碼的執行結果:

main function start
Instance of 'Future<String>'
main function end
// 3s後沒有拿到結果,可是咱們捕獲到了異常
Exception: 網絡請求出現錯誤

複製代碼

2.1.3. Future使用補充

補充一:上面案例的小結

咱們經過一個案例來學習了一些Future的使用過程:

  • 一、建立一個Future(多是咱們建立的,也多是調用內部API或者第三方API獲取到的一個Future,總之你須要獲取到一個Future實例,Future一般會對一些異步的操做進行封裝);
  • 二、經過.then(成功回調函數)的方式來監聽Future內部執行完成時獲取到的結果;
  • 三、經過.catchError(失敗或異常回調函數)的方式來監聽Future內部執行失敗或者出現異常時的錯誤信息;

補充二:Future的兩種狀態

事實上Future在執行的整個過程當中,咱們一般把它劃分紅了兩種狀態:

狀態一:未完成狀態(uncompleted)

  • 執行Future內部的操做時(在上面的案例中就是具體的網絡請求過程,咱們使用了延遲來模擬),咱們稱這個過程爲未完成狀態

狀態二:完成狀態(completed)

  • 當Future內部的操做執行完成,一般會返回一個值,或者拋出一個異常。
  • 這兩種狀況,咱們都稱Future爲完成狀態。

Dart官網有對這兩種狀態解析,之因此貼出來是區別於Promise的三種狀態

dart官網

補充三:Future的鏈式調用

上面代碼咱們能夠進行以下的改進:

  • 咱們能夠在then中繼續返回值,會在下一個鏈式的then調用回調函數中拿到返回的結果
import "dart:io";

main(List<String> args) {
  print("main function start");

  getNetworkData().then((value1) {
    print(value1);
    return "content data2";
  }).then((value2) {
    print(value2);
    return "message data3";
  }).then((value3) {
    print(value3);
  });

  print("main function end");
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    // 再也不返回結果,而是出現異常
     return "network data1";
  });
}

複製代碼

打印結果以下:

main function start
main function end
// 3s後拿到結果
network data1
content data2
message data3

複製代碼

補充四:Future其餘API

Future.value(value)

  • 直接獲取一個完成的Future,該Future會直接調用then的回調函數
main(List<String> args) {
  print("main function start");

  Future.value("哈哈哈").then((value) {
    print(value);
  });

  print("main function end");
}

複製代碼

打印結果以下:

main function start
main function end
哈哈哈

複製代碼

疑惑:爲何當即執行,可是哈哈哈是在最後打印的呢?

  • 這是由於Future中的then會做爲新的任務會加入到事件隊列中(Event Queue),加入以後你確定須要排隊執行了

Future.error(object)

  • 直接獲取一個完成的Future,可是是一個發生異常的Future,該Future會直接調用catchError的回調函數
main(List<String> args) {
  print("main function start");

  Future.error(Exception("錯誤信息")).catchError((error) {
    print(error);
  });

  print("main function end");
}

複製代碼

打印結果以下:

main function start
main function end
Exception: 錯誤信息

複製代碼

Future.delayed(時間, 回調函數)

  • 在延遲必定時間時執行回調函數,執行完回調函數後會執行then的回調;
  • 以前的案例,咱們也可使用它來模擬,可是直接學習這個API會讓你們更加疑惑;
main(List<String> args) {
  print("main function start");

  Future.delayed(Duration(seconds: 3), () {
    return "3秒後的信息";
  }).then((value) {
    print(value);
  });

  print("main function end");
}

複製代碼

2.2. await、async

2.2.1. 理論概念理解

若是你已經徹底搞懂了Future,那麼學習await、async應該沒有什麼難度。

await、async是什麼呢?

  • 它們是Dart中的關鍵字(你這不是廢話嗎?廢話也仍是要強調的,萬一你用它作變量名呢,無辜臉。)
  • 它們可讓咱們用同步的代碼格式,去實現異步的調用過程
  • 而且,一般一個async的函數會返回一個Future(彆着急,立刻就看到代碼了)。

咱們已經知道,Future能夠作到不阻塞咱們的線程,讓線程繼續執行,而且在完成某個操做時改變本身的狀態,而且回調then或者errorCatch回調。

如何生成一個Future呢?

  • 一、經過咱們前面學習的Future構造函數,或者後面學習的Future其餘API均可以。
  • 二、還有一種就是經過async的函數。

2.2.2. 案例代碼演練

Talk is cheap. Show me the code.

咱們來對以前的Future異步處理代碼進行改造,改爲await、async的形式。

咱們知道,若是直接這樣寫代碼,代碼是不能正常執行的:

  • 由於Future.delayed返回的是一個Future對象,咱們不能把它當作同步的返回數據:"network data"去使用
  • 也就是咱們不能把這個異步的代碼當作同步同樣去使用!
import "dart:io";

main(List<String> args) {
  print("main function start");
  print(getNetworkData());
  print("main function end");
}

String getNetworkData() {
  var result = Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return  "請求到的數據:" + result;
}

複製代碼

如今我使用await修改下面這句代碼:

  • 你會發現,我在Future.delayed函數前加了一個await。
  • 一旦有了這個關鍵字,那麼這個操做就會等待Future.delayed的執行完畢,而且等待它的結果。
String getNetworkData() {
  var result = await Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return  "請求到的數據:" + result;
}

複製代碼

修改後執行代碼,會看到以下的錯誤:

  • 錯誤很是明顯:await關鍵字必須存在於async函數中。
  • 因此咱們須要將getNetworkData函數定義成async函數。

image-20190913153558169

繼續修改代碼以下:

  • 也很是簡單,只須要在函數的()後面加上一個async關鍵字就能夠了
String getNetworkData() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return  "請求到的數據:" + result;
}

複製代碼

運行代碼,依然報錯(心想:你妹啊):

  • 錯誤很是明顯:使用async標記的函數,必須返回一個Future對象。
  • 因此咱們須要繼續修改代碼,將返回值寫成一個Future。

image-20190913153648117

繼續修改代碼以下:

Future<String> getNetworkData() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return "請求到的數據:" + result;
}

複製代碼

這段代碼應該是咱們理想當中執行的代碼了

  • 咱們如今能夠像同步代碼同樣去使用Future異步返回的結果;
  • 等待拿到結果以後和其餘數據進行拼接,而後一塊兒返回;
  • 返回的時候並不須要包裝一個Future,直接返回便可,可是返回值會默認被包裝在一個Future中;

2.3. 讀取json案例

我這裏給出了一個在Flutter項目中,讀取一個本地的json文件,而且轉換成模型對象,返回出去的案例;

這個案例做爲你們學習前面Future和await、async的一個參考,我並不打算展開來說,由於須要用到Flutter的相關知識;

後面我會在後面的案例中再次講解它在Flutter中我使用的過程當中;

讀取json案例代碼(瞭解一下便可)

import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:async';

main(List<String> args) {
  getAnchors().then((anchors) {
    print(anchors);
  });
}

class Anchor {
  String nickname;
  String roomName;
  String imageUrl;

  Anchor({
    this.nickname,
    this.roomName,
    this.imageUrl
  });

  Anchor.withMap(Map<String, dynamic> parsedMap) {
    this.nickname = parsedMap["nickname"];
    this.roomName = parsedMap["roomName"];
    this.imageUrl = parsedMap["roomSrc"];
  }
}

Future<List<Anchor>> getAnchors() async {
  // 1.讀取json文件
  String jsonString = await rootBundle.loadString("assets/yz.json");

  // 2.轉成List或Map類型
  final jsonResult = json.decode(jsonString);

  // 3.遍歷List,而且轉成Anchor對象放到另外一個List中
  List<Anchor> anchors = new List();
  for (Map<String, dynamic> map in jsonResult) {
    anchors.add(Anchor.withMap(map));
  }
  return anchors;
}

複製代碼

三. Dart的異步補充

3.1. 任務執行順序

3.1.1. 認識微任務隊列

在前面學習學習中,咱們知道Dart中有一個事件循環(Event Loop)來執行咱們的代碼,裏面存在一個事件隊列(Event Queue),事件循環不斷從事件隊列中取出事件執行。

可是若是咱們嚴格來劃分的話,在Dart中還存在另外一個隊列:微任務隊列(Microtask Queue)。

  • 微任務隊列的優先級要高於事件隊列;
  • 也就是說事件循環都是優先執行微任務隊列中的任務,再執行 事件隊列 中的任務;

那麼在Flutter開發中,哪些是放在事件隊列,哪些是放在微任務隊列呢?

  • 全部的外部事件任務都在事件隊列中,如IO、計時器、點擊、以及繪製事件等;
  • 而微任務一般來源於Dart內部,而且微任務很是少。這是由於若是微任務很是多,就會形成事件隊列排不上隊,會阻塞任務隊列的執行(好比用戶點擊沒有反應的狀況);

說道這裏,你可能已經有點凌亂了,在Dart的單線程中,代碼究竟是怎樣執行的呢?

  • 一、Dart的入口是main函數,因此main函數中的代碼會優先執行;
  • 二、main函數執行完後,會啓動一個事件循環(Event Loop)就會啓動,啓動後開始執行隊列中的任務;
  • 三、首先,會按照先進先出的順序,執行 微任務隊列(Microtask Queue)中的全部任務;
  • 四、其次,會按照先進先出的順序,執行 事件隊列(Event Queue)中的全部任務;

代碼執行順序

3.1.2. 如何建立微任務

在開發中,咱們能夠經過dart中async下的scheduleMicrotask來建立一個微任務:

import "dart:async";

main(List<String> args) {
  scheduleMicrotask(() {
    print("Hello Microtask");
  });
}
複製代碼

在開發中,若是咱們有一個任務不但願它放在Event Queue中依次排隊,那麼就能夠建立一個微任務了。

Future的代碼是加入到事件隊列仍是微任務隊列呢?

Future中一般有兩個函數執行體:

  • Future構造函數傳入的函數體
  • then的函數體(catchError等同看待)

那麼它們是加入到什麼隊列中的呢?

  • Future構造函數傳入的函數體放在事件隊列中
  • then的函數體要分紅三種狀況:
  • 狀況一:Future沒有執行完成(有任務須要執行),那麼then會直接被添加到Future的函數執行體後;
  • 狀況二:若是Future執行完後就then,該then的函數體被放到如微任務隊列,當前Future執行完後執行微任務隊列;
  • 狀況三:若是Future世鏈式調用,意味着then未執行完,下一個then不會執行;
// future_1加入到eventqueue中,緊隨其後then_1被加入到eventqueue中
Future(() => print("future_1")).then((_) => print("then_1"));

// Future沒有函數執行體,then_2被加入到microtaskqueue中
Future(() => null).then((_) => print("then_2"));

// future_三、then_3_a、then_3_b依次加入到eventqueue中
Future(() => print("future_3")).then((_) => print("then_3_a")).then((_) => print("then_3_b"));
複製代碼

3.1.3. 代碼執行順序

咱們根據前面的規則來學習一個極的代碼執行順序案例:

import "dart:async";

main(List<String> args) {
  print("main start");

  Future(() => print("task1"));
	
  final future = Future(() => null);

  Future(() => print("task2")).then((_) {
    print("task3");
    scheduleMicrotask(() => print('task4'));
  }).then((_) => print("task5"));

  future.then((_) => print("task6"));
  scheduleMicrotask(() => print('task7'));

  Future(() => print('task8'))
    .then((_) => Future(() => print('task9')))
    .then((_) => print('task10'));

  print("main end");
}

複製代碼

代碼執行的結果是:

main start
main end
task7
task1
task6
task2
task3
task5
task4
task8
task9
task10

複製代碼

代碼分析:

  • 一、main函數先執行,因此main startmain end先執行,沒有任何問題;
  • 二、main函數執行過程當中,會將一些任務分別加入到EventQueueMicrotaskQueue中;
  • 三、task7經過scheduleMicrotask函數調用,因此它被最先加入到MicrotaskQueue,會被先執行;
  • 四、而後開始執行EventQueue,task1被添加到EventQueue中被執行;
  • 五、經過final future = Future(() => null);建立的future的then被添加到微任務中,微任務直接被優先執行,因此會執行task6;
  • 六、一次在EventQueue中添加task二、task三、task5被執行;
  • 七、task3的打印執行完後,調用scheduleMicrotask,那麼在執行完此次的EventQueue後會執行,因此在task5後執行task4(注意:scheduleMicrotask的調用是做爲task3的一部分代碼,因此task4是要在task5以後執行的)
  • 八、task八、task九、task10一次添加到EventQueue被執行;

事實上,上面的代碼執行順序有可能出如今面試中,咱們開發中一般不會出現這種複雜的嵌套,而且須要徹底搞清楚它的執行順序;

可是,瞭解上面的代碼執行順序,會讓你對EventQueuemicrotaskQueue有更加深入的理解。

3.2. 多核CPU的利用

3.2.1. Isolate的理解

在Dart中,有一個Isolate的概念,它是什麼呢?

  • 咱們已經知道Dart是單線程的,這個線程有本身能夠訪問的內存空間以及須要運行的事件循環;
  • 咱們能夠將這個空間系統稱之爲是一個Isolate;
  • 好比Flutter中就有一個Root Isolate,負責運行Flutter的代碼,好比UI渲染、用戶交互等等;

在 Isolate 中,資源隔離作得很是好,每一個 Isolate 都有本身的 Event Loop 與 Queue,

  • Isolate 之間不共享任何資源,只能依靠消息機制通訊,所以也就沒有資源搶佔問題。

可是,若是隻有一個Isolate,那麼意味着咱們只能永遠利用一個線程,這對於多核CPU來講,是一種資源的浪費。

若是在開發中,咱們有很是多耗時的計算,徹底能夠本身建立Isolate,在獨立的Isolate中完成想要的計算操做。

如何建立Isolate呢?

建立Isolate是比較簡單的,咱們經過Isolate.spawn就能夠建立了:

import "dart:isolate";

main(List<String> args) {
  Isolate.spawn(foo, "Hello Isolate");
}

void foo(info) {
  print("新的isolate:$info");
}

複製代碼

3.2.2. Isolate通訊機制

可是在真實開發中,咱們不會只是簡單的開啓一個新的Isolate,而不關心它的運行結果:

  • 咱們須要新的Isolate進行計算,而且將計算結果告知Main Isolate(也就是默認開啓的Isolate);
  • Isolate 經過發送管道(SendPort)實現消息通訊機制;
  • 咱們能夠在啓動併發Isolate時將Main Isolate的發送管道做爲參數傳遞給它;
  • 併發在執行完畢時,能夠利用這個管道給Main Isolate發送消息;
import "dart:isolate";

main(List<String> args) async {
  // 1.建立管道
  ReceivePort receivePort= ReceivePort();

  // 2.建立新的Isolate
  Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);

  // 3.監聽管道消息
  receivePort.listen((data) {
    print('Data:$data');
    // 再也不使用時,咱們會關閉管道
    receivePort.close();
    // 須要將isolate殺死
    isolate?.kill(priority: Isolate.immediate);
  });
}

void foo(SendPort sendPort) {
  sendPort.send("Hello World");
}

複製代碼

可是咱們上面的通訊變成了單向通訊,若是須要雙向通訊呢?

  • 事實上雙向通訊的代碼會比較麻煩;
  • Flutter提供了支持併發計算的compute函數,它內部封裝了Isolate的建立和雙向通訊;
  • 利用它咱們能夠充分利用多核心CPU,而且使用起來也很是簡單;

注意:下面的代碼不是dart的API,而是Flutter的API,因此只有在Flutter項目中才能運行

main(List<String> args) async {
  int result = await compute(powerNum, 5);
  print(result);
}

int powerNum(int num) {
  return num * num;
}

複製代碼

備註:全部內容首發於公衆號,以後除了Flutter也會更新其餘技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些本身的學習心得等,歡迎你們關注

公衆號
相關文章
相關標籤/搜索