Flutter 中的異步編程總結

Flutter 中的異步編程總結html

1、 Dart 中的事件循環模型

Dart 是一種單線程模型運行語言,其運行原理以下圖所示:編程

Dart 在單線程中是以消息循環機制來運行的,其中包含兩個任務隊列,一個是「微任務隊列」 microtask queue,另外一個叫作「事件隊列」 event queue。 從圖中能夠發現,微任務隊列的執行優先級高於事件隊列。bash

如今咱們來介紹一下Dart線程運行過程,如上圖中所示,入口函數 main() 執行完後,消息循環機制便啓動了。 首先會按照先進先出的順序逐個執行微任務隊列中的任務,當全部微任務隊列執行完後便開始執行事件隊列中的任務,事件任務執行完畢後再去執行微任務, 如此循環往復,生生不息。微信

在Dart中,全部的外部事件任務都在事件隊列中,如IO、計時器、點擊、以及繪製事件等,而微任務一般來源於Dart內部,而且微任務很是少, 之因此如此,是由於微任務隊列優先級高,若是微任務太多,執行時間總和就越久,事件隊列任務的延遲也就越久, 對於GUI應用來講最直觀的表現就是比較卡,因此必須得保證微任務隊列不會太長。網絡

在事件循環中,當某個任務發生異常並無被捕獲時,程序並不會退出,而直接致使的結果是當前任務的後續代碼就不會被執行了, 也就是說一個任務中的異常是不會影響其它任務執行的。併發

咱們能夠看出,將任務加入到MicroTask中能夠被儘快執行,但也須要注意,當事件循環在處理MicroTask隊列時,Event隊列會被卡住,應用程序沒法處理鼠標單擊、I/O消息等等事件。 同時,當事件循壞出現異常時,dart 中也能夠經過 try/catch/finally 來捕獲異常。app

2、任務調度

基於上述理論,看一下任務調度less

2.1 添加到 MicroTask 任務隊列 兩種方法:

import  'dart:async';
void  myTask(){
  print("this is my task");
}
void  main() {
  /// 1. 使用 scheduleMicrotask 方法添加
  scheduleMicrotask(myTask);
  /// 2. 使用Future對象添加
  new  Future.microtask(myTask);
}
複製代碼

兩個 task 都會被執行,控制檯輸出:異步

this is my task
this is my task
複製代碼

2.2 添加到 Event 隊列。

import  'dart:async';

void  myTask(){
  print("this is my event task");
}

void  main() {
  new  Future(myTask);
}
複製代碼

控制檯輸出:async

this is my event task
複製代碼

示例:

import  'dart:async';

void  main() {
  print("main start");

  new  Future((){
    print("this is my task");
  });

  new  Future.microtask((){
    print("this is microtask");
  });

  print("main stop");
}

複製代碼

控制檯輸出:

main start
main stop
this is microtask
this is my task
複製代碼

能夠看到,代碼的運行順序並非按照咱們的編寫順序來的,將任務添加到隊列並不等於馬上執行,它們是異步執行的,當前main方法中的代碼執行完以後,纔會去執行隊列中的任務,且MicroTask隊列運行在Event隊列以前。

2.三、延時任務

new  Future.delayed(new  Duration(seconds:1),(){
    print('task delayed');
});

複製代碼

使用 Future.delayed 可使用延時任務,可是延時任務不必定準

import  'dart:async';
import  'dart:io';

void  main() {
  var now = DateTime.now();

  print("程序運行開始時間:" + now.toString());

  new Future.delayed(new  Duration(seconds:1),(){
    now = DateTime.now();
    print('延時任務執行時間:' + now.toString());
  });

  new Future((){
    // 模擬耗時5秒
    sleep(Duration(seconds:5));
    now = DateTime.now();
    print("5s 延時任務執行時間:" + now.toString());
  });

  now = DateTime.now();
  print("程序結束執行時間:" + now.toString());
}

複製代碼

輸出結果:

程序運行開始時間:2019-09-11 20:46:18.321738
程序結束執行時間:2019-09-11 20:46:18.329178
5s 延時任務執行時間:2019-09-11 20:46:23.330951
延時任務執行時間:2019-09-11 20:46:23.345323
複製代碼

能夠看到,5s 的延時任務,確實實在當前程序運行以後的 5s 後獲得了執行,可是另外一個延時任務,是在當前延時的基礎上又延時執行的, 也就是,延時任務必須等前面的耗時任務執行完,才獲得執行,這將致使延時任務延時並不許。

3、Future 與 FutureBuilder

Dart類庫有很是多的返回Future或者Stream對象的函數。 這些函數被稱爲異步函數:它們只會在設置好一些耗時操做以後返回,好比像 IO操做。而不是等到這個操做完成。

Future與JavaScript中的Promise很是類似,表示一個異步操做的最終完成(或失敗)及其結果值的表示。簡單來講,它就是用於處理異步操做的,異步處理成功了就執行成功的操做,異步處理失敗了就捕獲錯誤或者中止後續操做。一個Future只會對應一個結果,要麼成功,要麼失敗。

因爲自己功能較多,這裏咱們只介紹其經常使用的API及特性。還有,請記住,Future 的全部API的返回值仍然是一個Future對象,因此能夠很方便的進行鏈式調用。

3.1 Future 使用

建立方法: Future的幾種建立方法

Future() Future.microtask() Future.sync() Future.value() Future.delayed() Future.error()

Future 和 Future.microtask 和 Future.delayed 上面已經演示過了。 Future.sync() 表示同步方法,會被當即執行: 如:

import  'dart:async';

void  main() {
  print("main start");

  new  Future.sync((){
    print("sync task");
  });

  new  Future((){
    print("async task");
  });

  print("main stop");
}

複製代碼

輸出:

main start
sync task
main stop
async task

複製代碼

3.2 Future 中的回調

當Future中的任務完成後,咱們每每須要一個回調,這個回調當即執行,不會被添加到事件隊列。 如:

import 'dart:async';

void main() {
  print("main start");


  Future fut =new Future.value(18);
  // 使用then註冊回調
  fut.then((res){
    print(res);
  });

  // 鏈式調用,能夠跟多個then,註冊多個回調
  new Future((){
    print("async task");
  }).then((res){
    print("async task complete");
  }).then((res){
    print("async task after");
  });

  print("main stop");
}

複製代碼

輸出結果:

main start
main stop
18
async task
async task complete
async task after
複製代碼

使用 catchError 捕獲異常:

import 'dart:async';

void main() {
  print("main start");

  Future.delayed(new Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data){
    //執行成功會走到這裏  
    print("success");
  }).catchError((e){
    //執行失敗會走到這裏
    print(e);
  });

  print("main stop");
}

複製代碼

輸出:

main start
main stop
Assertion failed
複製代碼

在本示例中,咱們在異步任務中拋出了一個異常,then的回調函數將不會被執行,取而代之的是 catchError回調函數將被調用;可是,並非只有 catchError回調才能捕獲錯誤,then方法還有一個可選參數onError,咱們也能夠它來捕獲異常:

import 'dart:async';

void main() {
  print("main start2");

  Future.delayed(new Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data) {
    print("success");
  }, onError: (e) {
    print(e);
  });

  print("main stop2");
}

複製代碼

結果:

main start2
main stop2
Assertion failed
複製代碼

whenComplete 必定獲得執行:

import 'dart:async';

void main() {
  print("main start3");
  Future.delayed(new Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data){
    //執行成功會走到這裏
    print(data);
  }).catchError((e){
    //執行失敗會走到這裏
    print(e);
  }).whenComplete((){
    //不管成功或失敗都會走到這裏
    print("this is the end...");
  });
  print("main stop3");
}

複製代碼

結果:

main start3
main stop3
Assertion failed
this is the end...
複製代碼

Future.wait 表示多個任務都完成以後的回調。

import 'dart:async';

void main() {
  print("main start4");

  Future.wait([
    // 2秒後返回結果
    Future.delayed(new Duration(seconds: 2), () {
      return "hello";
    }),
    // 4秒後返回結果
    Future.delayed(new Duration(seconds: 4), () {
      return " world";
    })
  ]).then((results){
    print(results[0]+results[1]);
  }).catchError((e){
    print(e);
  });

  print("main stop4");
}

複製代碼

結果:

main start4
main stop4
hello world
複製代碼

3.3 FutureBuilder 的使用

不少時候咱們會依賴一些異步數據來動態更新UI,好比在打開一個頁面時咱們須要先從互聯網上獲取數據,在獲取數據的過程當中咱們顯式一個加載框,等獲取到數據時咱們再渲染頁面;又好比咱們想展現Stream(好比文件流、互聯網數據接收流)的進度。固然,經過StatefulWidget咱們徹底能夠實現上述這些功能。 但因爲在實際開發中依賴異步數據更新UI的這種場景很是常見,所以Flutter專門提供了FutureBuilder和StreamBuilder兩個組件來快速實現這種功能。

FutureBuilder會依賴一個Future,它會根據所依賴的Future的狀態來動態構建自身。咱們看一下FutureBuilder構造函數:

FutureBuilder({
  this.future,
  this.initialData,
  @required this.builder,
})
複製代碼
  • future:FutureBuilder依賴的Future,一般是一個異步耗時任務。

  • initialData:初始數據,用戶設置默認數據。

  • builder:Widget構建器;該構建器會在Future執行的不一樣階段被屢次調用,構建器簽名以下:

Function (BuildContext context, AsyncSnapshot snapshot)
複製代碼

snapshot會包含當前異步任務的狀態信息及結果信息 ,好比咱們能夠經過snapshot.connectionState獲取異步任務的狀態信息、經過snapshot.hasError判斷異步任務是否有錯誤等等,完整的定義讀者能夠查看AsyncSnapshot類定義。

示例:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  Future<String> mockNetworkData() async {
    return Future.delayed(Duration(seconds: 2), () => "這是網絡請求的數據。。。");
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<String>(
          future: mockNetworkData(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            // 請求已結束
            if (snapshot.connectionState == ConnectionState.done) {
              if (snapshot.hasError) {
                // 請求失敗,顯示錯誤
                return Text("Error: ${snapshot.error}");
              } else {
                // 請求成功,顯示數據
                return Text("Contents: ${snapshot.data}");
              }
            } else {
              // 請求未結束,顯示loading
              return CircularProgressIndicator();
            }
          },
        ),
      ),
    // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

複製代碼

效果:

4、async/await

在Dart1.9中加入了async和await關鍵字,有了這兩個關鍵字,咱們能夠更簡潔的編寫異步代碼,而不須要調用Future相關的API 將 async 關鍵字做爲方法聲明的後綴時,具備以下意義

  • 被修飾的方法會將一個 Future 對象做爲返回值
  • 該方法會同步執行其中的方法的代碼直到第一個 await 關鍵字,而後它暫停該方法其餘部分的執行;
  • 一旦由 await 關鍵字引用的 Future 任務執行完成,await的下一行代碼將當即執行。
// 導入io庫,調用sleep函數
import 'dart:io';

// 模擬耗時操做,調用sleep函數睡眠2秒
doTask() async{
  await sleep(const Duration(seconds:2));
  return "Ok";
}

// 定義一個函數用於包裝
test() async {
  var r = await doTask();
  print(r);
}

void main(){
  print("main start");
  test();
  print("main end");
}
複製代碼

結果:

main start
main end
Ok
複製代碼

5、Stream 與 StreamBuilder

Stream 也是用於接收異步事件數據,和Future 不一樣的是,它能夠接收多個異步操做的結果(成功或失敗)。 也就是說,在執行異步任務時,能夠經過屢次觸發成功或失敗事件來傳遞結果數據或錯誤異常。 Stream 經常使用於會屢次讀取數據的異步任務場景,如網絡內容下載、文件讀寫等。

5.1 Stream 的使用

void main(){
  print("main start");
  Stream.fromFutures([
    // 1秒後返回結果
    Future.delayed(new Duration(seconds: 1), () {
      return "hello 1";
    }),
    // 拋出一個異常
    Future.delayed(new Duration(seconds: 2),(){
      throw AssertionError("Error");
    }),
    // 3秒後返回結果
    Future.delayed(new Duration(seconds: 3), () {
      return "hello 3";
    })
  ]).listen((data){
    print(data);
  }, onError: (e){
    print(e.message);
  },onDone: (){

  });
  print("main end");
}

複製代碼

結果:

main start
main end
hello 1
Error
hello 3
複製代碼

5.1 StreamBuilder 的使用

咱們知道,在Dart中Stream 也是用於接收異步事件數據,和Future 不一樣的是,它能夠接收多個異步操做的結果,它經常使用於會屢次讀取數據的異步任務場景,如網絡內容下載、文件讀寫等。StreamBuilder正是用於配合Stream來展現流上事件(數據)變化的UI組件。 下面看一下StreamBuilder的默認構造函數:

StreamBuilder({
  Key key,
  this.initialData,
  Stream<T> stream,
  @required this.builder,
})
複製代碼

能夠看到和FutureBuilder的構造函數只有一點不一樣:前者須要一個future,然後者須要一個stream。

示例:

Stream<int> counter() {
    return Stream.periodic(Duration(seconds: 1), (i) {
      return i;
    });
  }
複製代碼
Widget buildStream (BuildContext context) {
      return StreamBuilder<int>(
        stream: counter(), //
        //initialData: ,// a Stream<int> or null
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
          if (snapshot.hasError)
            return Text('Error: ${snapshot.error}');
          switch (snapshot.connectionState) {
            case ConnectionState.none:
              return Text('沒有Stream');
            case ConnectionState.waiting:
              return Text('等待數據...');
            case ConnectionState.active:
              return Text('active: ${snapshot.data}');
            case ConnectionState.done:
              return Text('Stream已關閉');
          }
          return null; // unreachable
        },
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body:Center(
        child:  buildStream(context),
      )
    // This trailing comma makes auto-formatting nicer for build methods.
    );
複製代碼

效果:

6、isolate

Dart是基於單線程模型的語言。可是在開發當中咱們常常會進行耗時操做好比網絡請求,這種耗時操做會堵塞咱們的代碼,因此在Dart也有併發機制,名叫isolate。 APP的啓動入口main函數就是一個相似Android主線程的一個主isolate。和Java的Thread不一樣的是,Dart中的isolate沒法共享內存,相似於Android中的多進程。

6.1 使用 spawnUri 建立 isolate

spawnUri方法有三個必須的參數,第一個是Uri,指定一個新Isolate代碼文件的路徑,第二個是參數列表,類型是List,第三個是動態消息。 須要注意,用於運行新Isolate的代碼文件中,必須包含一個main函數,它是新Isolate的入口方法,該main函數中的args參數列表, 正對應spawnUri中的第二個參數。如不須要向新Isolate中傳參數,該參數可傳空List 主Isolate中的代碼:

import 'dart:isolate';


void main() {
  print("main isolate start");
  create_isolate();
  print("main isolate stop");
}

// 建立一個新的 isolate
create_isolate() async{
  ReceivePort rp = new ReceivePort();
  ///建立發送端
  SendPort port1 = rp.sendPort;
  ///建立新的 isolate ,並傳遞了發送端口。
  Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1);

  SendPort port2;
  rp.listen((message){
    print("main isolate message: $message");
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,"這條信息是 main isolate 發送的"]);
    }
  });

  // 能夠在適當的時候,調用如下方法殺死建立的 isolate
  // newIsolate.kill(priority: Isolate.immediate);
}

複製代碼

在主 isolate 文件的同級路徑下,新建一個 other_task.dart ,內容以下:

import 'dart:isolate';
import  'dart:io';


///接收到了發送端口。
void main(args, SendPort port1) {
  print("isolate_1 start");
  print("isolate_1 args: $args");

  ReceivePort receivePort = new ReceivePort();
  SendPort port2 = receivePort.sendPort;

  receivePort.listen((message){
    print("isolate_1 message: $message");
  });

  // 將當前 isolate 中建立的SendPort發送到主 isolate中用於通訊
  port1.send([0, port2]);
  // 模擬耗時5秒
  sleep(Duration(seconds:5));
  port1.send([1, "isolate_1 任務完成"]);
  print("isolate_1 stop");
}

複製代碼

運行主 isolate,輸出結果:

main isolate start
main isolate stop
isolate_1 start
isolate_1 args: [hello, isolate, this is args]
main isolate message: [0, SendPort]
isolate_1 stop
main isolate message: [1, isolate_1 任務完成]
isolate_1 message: [1, 這條信息是 main isolate 發送的]
複製代碼

isolate 之間的通訊時經過 ReceivePort 來完成的,ReceivePort 能夠理解爲一根水管,消息的傳遞方向時固定的,把這個水管的發送端發送給對方, 對方就能經過這個水管發送消息。

6.2 經過 spawn 建立 isolate

除了使用spawnUri,更經常使用的是使用spawn方法來建立新的Isolate,咱們一般但願將新建立的Isolate代碼和main Isolate代碼寫在同一個文件, 且不但願出現兩個main函數,而是將指定的耗時函數運行在新的Isolate,這樣作有利於代碼的組織和代碼的複用。spawn方法有兩個必須的參數, 第一個是須要運行在新Isolate的耗時函數,第二個是動態消息,該參數一般用於傳送主Isolate的SendPort對象。

示例:

import 'dart:isolate';
import  'dart:io';

void main() {
  print("主程序開始");
  create_isolate();
  print("主程序結束");
}

// 建立一個新的 isolate
create_isolate() async{
  ReceivePort rp = new ReceivePort();
  SendPort port1 = rp.sendPort;

  Isolate newIsolate = await Isolate.spawn(doWork, port1);

  SendPort port2;
  rp.listen((message){
    print("main isolate message: $message");
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,"這條信息是 main isolate 發送的"]);
    }
  });
}

// 處理耗時任務
void doWork(SendPort port1){
  print("new isolate start");
  ReceivePort rp2 = new ReceivePort();
  SendPort port2 = rp2.sendPort;

  rp2.listen((message){
    print("doWork message: $message");
  });

  // 將新isolate中建立的SendPort發送到主isolate中用於通訊
  port1.send([0, port2]);
  // 模擬耗時5秒
  sleep(Duration(seconds:5));
  port1.send([1, "doWork 任務完成"]);

  print("new isolate end");
}

複製代碼

結果:

主程序開始
主程序結束
new isolate start
main isolate message: [0, SendPort]
new isolate end
main isolate message: [1, doWork 任務完成]
doWork message: [1, 這條信息是 main isolate 發送的]
複製代碼

參考:

juejin.im/post/5cdbf2… juejin.im/post/5c876e… book.flutterchina.club/chapter2/th… book.flutterchina.club/chapter7/fu… book.flutterchina.club/chapter1/da…

最後

歡迎關注「Flutter 編程開發」微信公衆號 。

相關文章
相關標籤/搜索