Flutter--Dart基礎語法(四)異步

前言

Flutter 是 Google 開源的 UI 工具包,幫助開發者通過一套代碼庫高效構建多平臺精美應用,Flutter 開源、免費,擁有寬鬆的開源協議,支持移動、Web、桌面和嵌入式平臺。

Flutter是使用Dart語言開發的跨平臺移動UI框架,通過自建繪製引擎,能高性能、高保真地進行Android和IOS開發。Flutter採用Dart語言進行開發,而並非Java,Javascript這類熱門語言,這是Flutter團隊對當前熱門的10多種語言慎重評估後的選擇。因爲Dart囊括了多數編程語言的優點,它更符合Flutter構建界面的方式。

本文主要就是簡單梳理一下Dart語言的一些基礎知識和語法。關於編程語言的基本語法無外乎那麼些內容,註釋、變量、數據類型、運算符、流程控制、函數、類、異常、文件、異步、常用庫等內容,相信大部分讀者都是有一定編程基礎的,所以本文就簡單地進行一個梳理,不做詳細的講解。大家也可以參考 Dart編程語言中文網

上一篇文章主要是寫了Dart語言的類和對象、泛型以及庫的使用,本文將接着上一篇文章繼續往後寫,本文將主要介紹Dart語言中的異步。關於Dart中的異步,本文主要內容來源於官網鏈接https://dart.dev/codelabs/async-await,官網基本都是英文的,所以本文轉載自 Flutter(五)之徹底搞懂Dart異步,該博文中的內容基本與官網中介紹的一致。

一、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!');
      }
    });
  },
) 

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

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

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

二. Dart的異步操作

  • Dart中的異步操作主要使用Future以及async、await。
  • 如果你之前有過前端的ES6、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";
  });
} 

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

  • 1、這一次的代碼順序執行,沒有出現任何的阻塞現象;
  • 2、和之前直接打印結果不同,這次我們打印了一個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的使用過程:

  • 1、創建一個Future(可能是我們創建的,也可能是調用內部API或者第三方API獲取到的一個Future,總之你需要獲取到一個Future實例,Future通常會對一些異步的操作進行封裝);
  • 2、通過.then(成功回調函數)的方式來監聽Future內部執行完成時獲取到的結果;
  • 3、通過.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的回調函數
    • 疑惑:爲什麼立即執行,但是哈哈哈是在最後打印的呢?這是因爲Future中的then會作爲新的任務會加入到事件隊列中(Event Queue),加入之後你肯定需要排隊執行了 

      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.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呢?

  • 1、通過我們前面學習的Future構造函數,或者後面學習的Future其他API都可以。
  • 2、還有一種就是通過async的函數。

2.2.2. 案例代碼演練

Talk is cheapShow 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的執行完畢,並且等待它的結果。

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

  • 錯誤非常明顯: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的單線程中,代碼到底是怎樣執行的呢?

  • 1、Dart的入口是main函數,所以main函數中的代碼會優先執行;
  • 2、main函數執行完後,會啓動一個事件循環(Event Loop)就會啓動,啓動後開始執行隊列中的任務;
  • 3、首先,會按照先進先出的順序,執行 微任務隊列(Microtask Queue)中的所有任務;
  • 4、其次,會按照先進先出的順序,執行 事件隊列(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_3、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

代碼分析:

  • 1、main函數先執行,所以main startmain end先執行,沒有任何問題;
  • 2、main函數執行過程中,會將一些任務分別加入到EventQueueMicrotaskQueue中;
  • 3、task7通過scheduleMicrotask函數調用,所以它被最早加入到MicrotaskQueue,會被先執行;
  • 4、然後開始執行EventQueue,task1被添加到EventQueue中被執行;
  • 5、通過final future = Future(() => null);創建的future的then被添加到微任務中,微任務直接被優先執行,所以會執行task6;
  • 6、一次在EventQueue中添加task2、task3、task5被執行;
  • 7、task3的打印執行完後,調用scheduleMicrotask,那麼在執行完這次的EventQueue後會執行,所以在task5後執行task4(注意:scheduleMicrotask的調用是作爲task3的一部分代碼,所以task4是要在task5之後執行的)
  • 8、task8、task9、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;
}

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章