Flutter(二十二)——異步編程

前言

說到網絡,就一定會提到異步編程。對於涉及網絡的操作,在客戶端的開發中都是通過異步實現的。在Flutter裏,異步是用Future來修飾的,並且運行在event loop裏。

Flutter的異步特性和Android的Looper以及前端的event loop有點像,都是不斷地從事件隊列裏獲取事件然後運行,並通過異步操作有效防止一些耗時任務對主UI線程地影響。

isolate

Flutter中很重要的一個概念就是isolate,它是通過Flutter Engine層面的一個線程來實現的,而實現isolate的線程又是由Flutter管理和創建的。除了isolate所在的線程以外,還有其他的線程,它們跟Flutter的線程模型(Threading Mode)有關。在我們介紹完isolate的基本用法和概念之後,後面會專門由一節介紹Flutter Engine層線程模型相關知識。

所有的Dart代碼都是在isolate上運行的。通常情況下,我們的應用都是運作在main isolate中的,在必要時我們可以通過相關的API創建新的isolate,以便更好的利用CPU的特性,並以此提高效率。需要注意一點,多個isolate無法共享內存,必須通過相關的API通信纔可以。

event loop

另一個比較重要的概念是event loop。學過過JS前端知識的一定對event loop有所瞭解,理解它並不困難,而且Flutter簡單一些,下面是它的原理圖:
流程圖(1)運行App並執行main方法

(2)開始並優先處理microtask queue,直到隊列爲空。

(3)當microtask queue爲空後,開始處理event queue。如果event queue裏面有event,則執行,每執行一條在判斷此時新的microtask queue是否爲空,並且每一次只取出一個來執行。可以這樣理解,在處理所有event之前我們會做一些事情,並且會把這些事情放在microtask queue中。

(4)microtask queue和event queue都爲空,則App可以正常退出。

特別注意:當處理microtask queue時,event queue是會被阻塞的。所以microtask queue中應避免任務太多或長時間處理,否則將導致App的繪製和交互等行爲被卡住。所以,繪製和交互等應該作爲event存放在event queue中,這樣更合適。

我們直接來看一個例子,代碼如下:

_loopDemo(){
    print("開始");
    scheduleMicrotask(()=>print("microtask隊列1"));

    new Future.delayed(new Duration(seconds: 1),()=>print("future隊列1 delayed"));

    new Future(()=>print("future隊列2"))
      .then((_)=>print("future隊列2 then1"))
      .then((_){
        print("future隊列2 then 2");
        scheduleMicrotask(()=>print("future隊列2 then2中microtask隊列"));
    }).then((_)=>print("future隊列2 then3"))
        .then((_)=>print("future隊列2 then4"));

    scheduleMicrotask(()=>print("microtask隊列2"));

    new Future(()=>print("future隊列3"))
      .then((_)=>new Future(()=>print("future隊列3 then1 future")))
      .then((_)=>print("future隊列3 then2"))
      .then((_)=>print("future隊列3 then3"));

    new Future(()=>print("future隊列4"))
     .then((_){
       new Future(()=>print("future隊列4 then1 future"));
    }).then((_)=>print("future隊列4 then2"))
        .then((_)=>print("future隊列4 then3"));

    scheduleMicrotask(()=>print("microtask隊列3"));
    print("結束");

這裏,scheduleMicrotask代表流程圖中的microtask queue,future代表event queue,控制檯打印的結果如下:
打印這段代碼有幾點需要注意:

(1)Future.delayed表示延遲執行,在設置的延遲時間到了之後,纔會被放在event loop隊列尾部。

(2)Future.then裏的任務不會添加到event queue中,要保證異步任務的執行順序就一定要用then。

線程模型與isolate

上面已經介紹,isolate是通過Flutter Engine層面的一個線程來實現的,而實現isolate的線程是由Flutter管理和創建的。其實,Flutter Engine線程的創建和管理是由embedder(嵌入層)負責的,embeder是平臺引擎移植的中間代碼。下面就是Flutter Engine的運行架構:
dartVM可以看到它給我們提供了4種task Runner:

(1)Platform Task Runner

Platform Task Runner是Flutter Engine的主Task Runner,它不僅可以處理與Engine的交互,還可以處理來自Native(Android/IOS)平臺的交互。平臺的API都只能在主線程中被調用。每一個Flutter應用啓動的時間都會創建一個Engine實例,Engine創建的時候都會創建一個Platform Thread 供Platform Task Runner使用。即使Platform Thread被阻塞,也不會直接導致Flutter應用的卡頓。儘管如此,也不建議在這個Runner裏執行如此繁重的操作,長時間卡住Platform Thread,應用有可能會被系統的Watchdog強行終止。

(2)UI Task Runner

UI Task Runner不能想當然的理解爲像Android那樣是運行在主線程的,它其實是運行在線程對應到平臺的線程上的,屬於子線程。Root isolate在引擎啓動時綁定了不少Flutter所需要的方法,從而使其具備調度/提交/渲染幀的能力。在Root isolate通知Flutter Engine有幀需要被渲染後,渲染時就會生成Layer Tree並交給Flutter Engine。此時,僅生成了需要描繪的內容,然後才創建和更新Layer Tree,該Tree最終決定什麼內容會在屏幕上被繪製,因此 UI Task Runner如果過載會導致卡頓。

isolate可以理解爲單線程,如果運算量大,可以考慮採用獨立的isolate,單獨創建的isolate(非Root isolate)不支持綁定Flutter的功能,也不能調用,只能做數據運算。單獨創建的isolate的生命週期會受Root isolate控制,只要 Root isolate停止了,那麼其他的isolate也會停止。isolate運行的線程是Dart VM裏的線程池提供的。

UI Task Runner還可以處理來自Native Plugins的消息,timers,microtask,異步I/O操作。

(3)GPU Task Runner

GPU Task Runner被用於執行與設備GPU相關的調用,它可以將GPU Task Runner生成的Layer Tree所提供的信息轉換爲實際的GPU指令。GPU Task Runner的運行線程對應着平臺的子線程。UI Task Runner和GPU Task Runner在不同的線程上運行。GPU Runner會根據目前幀被執行的進度向UI Task Runner要求下一幀的數據。在任務繁重的時候,UI Task Runner會延遲任務進程。這種調度機制確保了GPU Task Runner不至於出現過載現象,同時避免了UI Task Runner不必要的消耗。如果在線程中耗時太久的話,就會造成Flutter應用的卡頓,因此在GPU Task Runner中,儘量不要做耗時的任務。例如,加載圖片的同時讀取圖片的數據,就不應該在GPU Task Runner中運行,而應該放在IO Task Runner中進行。

(4)IO Task Runner

IO Task Runner的運行線程也對應這平臺的子線程。當UI和GPU Task Runner都出現了過載的情況時,會導致Flutter應用卡頓。IO Task Runner就會負責做一些預先處理的讀取操作,然後再上報給GPU Task Runner,且只有GPU Task Runner可以訪問到GPU。簡單點說就是,IO Task Runner是GPU Task Runner的助手,可以減少GPU Task Runner額外的操作。IO Task Runner並不會阻塞Flutter。雖然在通過IO Task Runner加載圖片和資源的時候可能會有延遲,但是還是建議爲IO Task Runner單獨建立一個線程。

創建單獨的isolate

在UI Task Runner過載的情況下,可以創建單獨的isolate。單獨創建的isolate之間沒有共享內存,所以它們之間的唯一通信方式只能通過Port進行,而且Dart中的消息傳遞總是異步的。isolate與線程有着本質的區別,操作系統內的線程之間是可以共享內存的,而isolate不會,這是最爲關鍵的區別。那麼如何創建isolate呢,代碼如下:

 _isolateDemo() async{
    //isolate所需要的參數必須要有SendPort,SendPort有必須要要有ReceivePort來創建
    final receivePort=new ReceivePort();
    //創建新的isolate,這裏使用Isolatel.spawn函數創建
    await Isolate.spawn(_isolate, receivePort.sendPort);
    //發送消息
    var sendPort=await receivePort.first;
    var msg=await sendReceive(sendPort, "fpp");
    print("received $msg");
    msg=await sendReceive(sendPort, "bar");
    print("received $msg");
  }
//新isolate的入口函數
_isolate(SendPort replyTo) async{
  //實例話一個receivePort用於接收消息
  var port=new ReceivePort();
  //把它的sendPort發送給宿主isolate,以便宿主可以給它發消息
  replyTo.send(port.sendPort);
  //監聽消息,從Port裏獲取
  await for(var msg in port){
    var data=msg[0];
    SendPort replyTo=msg[1];
    replyTo.send("接受到了:"+data);
    if(data=="bar")port.close();
  }
}
//對某一個Port發送消息,並接受結果
Future sendReceive(SendPort port,msg){
  ReceivePort response=new ReceivePort();
  port.send([msg,response.sendPort]);
  return response.first;
}

如果你需要處理過度耗時的任務就可以使用如上方式進行創建isolate。

Stream事件流

Stream是與Flutter相關的一個重要概念,它首先是基於事件流來驅動並設計代碼的,然後監聽和訂閱相關事件,並且對事件的變化進行處理和響應。Stream不是Flutter中特有的,而是Dart語言中自帶的。我們可以把Stream想象成管道(pipe)的兩端,它只允許從一端插入數據並通過管道從另外一端流出數據。我們可以通過StreamController來控制Stream(事件源),比如StreamController提供了類型爲StreamSink,屬性爲Sink的控制器作爲入口。

Stream可以傳輸什麼?它支持任何類型數據的傳輸,包括基本值,事件,對象,集合等,即任何可能改變的數據都可以被Stream傳毒和觸發。當我們在傳輸數據時,可以通過listen函數監聽來自StreamController的屬性,在監聽之後,可以通過StreamSubscription訂閱對象並接受Stream發送數據變更的通知。

Stream也是異步處理的核心API,那麼,它與同爲異步處理的Future有何區別呢?答案時Future表示“將來”一次異步獲取得到的數據,而Stream是多次異步獲取得到的數據;Future將返回一個值,而Stream將返回多次值。在講Future時我們提到了FutureBuilder類,對於Stream,它有StreamBuilder類負責監聽Stream。當Stream數據流出時會自動重新構建組件,並通過Builder進行回調。

下面就是Stream的簡單例子,代碼如下:

class _MyHomePageState extends State<MyHomePage> {

  final StreamController<int> _streamController=StreamController<int>();
  int _counter=0;

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("數字的變化:"),
            StreamBuilder<int>(
              stream: _streamController.stream,
              initialData: 0,
              builder: (BuildContext context,AsyncSnapshot<int> snapshot){
                return Text(
                  '${snapshot.data}',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          _streamController.sink.add(++_counter);
        },
        tooltip: '自加',
        child: Icon(Icons.add),
      ),
    );
  }
}

上面就是要給簡單的計數程序,不過這裏是通過stream實現的,上面沒什麼難點,都是常用到的一些代碼,這裏就不做過多的贅述了。

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