Flutter (三) Dart 語言基礎詳解 (異步,生成器,隔離,元數據,註釋)

異步

簡介

對於異步不太瞭解,能夠直接看官網介紹;java

Dart 有一些語言特性來支持異步編程。最多見的特性是 async 方法和 await 表達式。web

Dart 庫中有不少返回 Future 或者 Stream 對象的方法。 這些方法是異步的 : 這些函數在設置完基本的操做後就返回了 , 而無需等待操做執行完成。 例如讀取一個文件 , 在打開文件後就返回了。編程

async 和 await

有兩種方式可使用 Future 對象中的數據:瀏覽器

使用 asyncawait 的代碼是異步的, 可是看起來有點像同步代碼。 例如,下面是一些使用 await 來 等待異步方法返回的示例:bash

await lookUpVersion();
複製代碼

要使用 await,其方法必須帶有 async 關鍵字:markdown

checkVersion() saync {
   var version =  await lookUpVersion();
    if(version == expectedVersion){
        // TODO ------
    } else {
         // TODO ------
    }
} 
複製代碼

可使用 try, catch, 和 finally 來處理使用 await 的異常:網絡

void getHttp(){
  try{
    var response = await Dio().get("http://www.baidu.com");
    print(response);
  }catch(e){
    print(e);
  }
}
複製代碼

聲明異步方法多線程

  • 一個 async 方法 是函數體被標記爲 async 的方法。 雖然異步方法的執行可能須要必定時間,可是 異步方法馬上返回 - 在方法體還沒執行以前就返回了。app

    void getHttp async {
        // TODO ---
    }
    複製代碼

在一個方法上添加 async 關鍵字,則這個方法返回值爲 Future。 例如,下面是一個返回字符串的同步方法:異步

String loadAppVersion() => "1.0.2"
複製代碼

若是使用 async 關鍵字,則該方法返回一個 Future,而且 認爲該函數是一個耗時的操做。

Futre<String> loadAppVersion() async  => "1.0.2"
複製代碼

注意,方法的函數體並不須要使用 Future API。 Dart 會自動在須要的時候建立 Future 對象。

推薦使用 async / await 而不是直接使用底層的特性

  • 好的習慣
void main() {
 //調用異步方法
 doAsync();
}

// 在函數上聲明瞭 async 代表這是一個異步方法
Future<bool> doAsync() async {
  try {
    // 這裏是一個模擬請求一個網絡耗時操做
    var result = await getHttp();
    //請求出來的結果
    return printResult(result);
  } catch (e) {
    print(e);
    return false;
  }
}
//將請求出來的結果打印出來
Future<bool> printResult(summary) {
  print(summary);
}

//開始模擬網絡請求 等待 5 秒返回一個字符串
getHttp() {
 return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
複製代碼
  • 壞的習慣寫法
void main() {
 doAsync();
}

Future<String> doAsync() async {
    return  getHttp().then((r){
      return printResult(r);
    }).catchError((e){
      print(e);
    });
}

Future<String> printResult(summary) {
  print(summary);
}

Future<String> getHttp() {
 return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
複製代碼

then,catchError,whenComplete

先來看一段代碼

void doAsyncs() async{
  //then catchError whenComplete
  new Future(() => futureTask()) // 異步任務的函數
      .then((m) => "1-:$m") // 任務執行完後的子任務
      .then((m) => print('2-$m')) // 其中m爲上個任務執行完後的返回的結果
      .then((_) => new Future.error('3-:error'))
      .then((m) => print('4-'))
      .whenComplete(() => print('5-')) //不是最後執行whenComplete,一般放到最後回調

// .catchError((e) => print(e))//若是不寫test默認實現一個返回true的test方法
      .catchError((e) => print('6-catchError:' + e), test: (Object o) {
    print('7-:' + o);
    return true; //返回true,會被catchError捕獲
// return false; //返回false,繼續拋出錯誤,會被下一個catchError捕獲
  })
  .then((_) => new Future.error('11-:error'))
      .then((m) => print('10-'))
      .catchError((e) => print('8-:' + e))
      ;
}

 futureTask() {
// throw 'error';
  return Future.delayed(Duration(seconds: 5),()  => "9-走去跑步");
}
複製代碼

你們猜一猜,看下是怎麼樣的一個執行流程;我直接放上答案吧,咱們來分析下

2-1-:9-走去跑步
5-
7-:3-:error
6-catchError:3-:error
8-:11-:error
複製代碼

當異步函數 futureTask() 執行完會在內存中保存 ‘9-走去跑步’ 而後繼續執行下一步 這個時候碰見了 then 如今會在內存中保存 「1-: 9-走去跑步 」 繼續執行 這個時候碰見了打印輸出 2-1-:9-走去跑步 。如今第一個打印出來了。接着執行下一個 then() 這個時候碰見了一個 error 異常,Dart 會把這個異常保存在內存直到碰見捕獲異常的地方。下面執行 whenComplete 這個函數 打印 5- 。而後碰見了一個捕獲異常的函數 catchError 若是 test 返回 true ,會被 catchError 捕獲 打印 7-:3-:error 6-catchError:3-:error。若是返回 false 只打印 7-:3-:error,會把 error 拋給下一個 catchError 。繼續執行 又碰見了一個 error 11-:error ,如今出現 error 了 因此 then 10- 就不會執行了 。最後就直接捕獲異常 打印 "8-11-error"。

分析完了,你們會了嗎?相信你們差很少會了!

Event-Looper

如下內容從官網所獲得

Dart 是單線程模型 Main, 也就沒有了所謂的主線程/子線程之分。

Dart 也是 Event-Loop 以及 Event - Queue 的模型,全部的事件都是經過 Event-loop 的依次執行。

而 Dart 的 Event Loop 就是:

  • 從 EventQueue 中獲取 Event

  • 處理 Event

  • 直到 EventQueue 爲空

    1553334610.jpg

而這些 Event 包括了用戶輸入,點擊, Timer, 文件 IO 等

1553334796.jpg

單線程模型

一旦某個 Dart 的函數開始執行,它將執行到這個函數結束,也就是 Dart 的函數不會被其餘 Dart 代碼打斷。

Dart 中沒有線程的概念,只有 isolate(隔離),每一個 isolate 都是隔離的,並不會共享內存。而一個 Dart 程序是在 Main isolate 的 main 函數開始,而在 Main 函數結束後,Main isolate 線程開始一個一個(one by one)的開始處理 Event Queue 中的每個 Event 。

asgrargargaw.webp

Event Queue 和 Microtask Queue

Dart 中的 Main Isolate 只有一個 Event - Looper,可是存在兩個 Event Queue : Event Queue 以及 Microtask Queue Microtask Queue 存在的意義是: 但願經過這個 Queue 來處理稍晚一些的事情,可是在下一個消息到來以前須要處理完的事情。 當 Event Looper 正在處理 Microtask Queue 中的 Event 時候,Event Queue 中的 Event 就中止了處理了,此時 App 不能繪製任何圖形,不能處理任何鼠標點擊,不能處理文件 IO 等等。

Event-Looper 挑選 Task 的執行順序爲:

  • 優先所有執行完 Microtask Queue.

  • 直到 Microtask Queue 爲空時,纔會執行 Event Queue 中的 Event.

    1941624-f7acc83a0816453f.png

Dart 中只能知道 Event 處理的前後順序,可是並不知道某個 Event 執行的具體時間點,由於它的處理模型是一個單線程循環,而不是基於時鐘調度(即它的執行只是按照 Event 處理完,就開始循環下一個 Event,而與Java 中的 Thread 調度不同,沒有時間調度的概念),也就是咱們既是指定另外一個 Delay Time 的 Task,但願它在預期的時間後開始執行,它有可能不會在那個時間執行,須要看是否前面的 Event 是否已經 Dequeue 。

任務調度

當有代碼能夠在後續任務執行的時候,有兩種方式,經過dart:async這個Lib中的API便可:

  • 使用 Future 類,能夠將任務加入到 Event Queue 的隊尾
  • 使用 scheduleMicrotask 函數,將任務加入到 Microtask Queue 隊尾

當使用 EventQueue 時,須要考慮清楚,儘可能避免 microtask queue 過於龐大,不然會阻塞其餘事件的處理

1941624c2926ff39b5a2ac9.webp

new Future()

先來看一段代碼

void testFuture() {
  Future f = new Future(() => print("1"));
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);
    
  f3.then((_) => print("2"));
  f2.then((_) {
    print("3");
    new Future(() => print("4"));
    f1.then((_) {
      print("5");
    });
  });
  f1.then((m) {
    print("6");
  });
  print("7");
}
複製代碼

你們來猜一猜上面的打印效果,打印結果是:

7
1
6
3
5
2
4
複製代碼

是否是沒有想到。下面咱們來分析下執行流程

  1. main 函數執行完的時候,f, f1, f2, f3 會進入事件隊列,這裏 print( 7 )不進入隊列 直接打印 7;
  2. 隊列裏面又執行 f 事件,發現 f 存在一個打印事件 print(1) 直接打印 1;
  3. 如今又執行 f1 事件,發現了一個 f1.then(m) => print('6') 直接打印 6;
  4. 執行到了 f2 事件,發現了 f2 事件裏面有一個 打印事件 3,先打印一個 3
    1. 而後碰見了一個新的時間 f4,放入新的事件隊列裏面。
    2. 如今又發現了一個 f1 的事件 print('5'),這裏的 f1 由於執行完了 返回的 null 因此要把該事件存放入 微隊列 裏面。微隊列裏面優先執行因此打印 5 ;
  5. 如今該執行到了 f2 事件了,發現了一個 print(2), 直接打印 2;
  6. 最後執行到了 f4 事件,發現了一個 print(4),直接打印 4;

注意:這裏的 f,f1,f2,f3... 進入事件隊列裏面要把它們理解成是一個總體,無論其它地方 還有 f1.then 或者 f2.then 。最後都是一個總體。

注意

  1. 使用 new Future 將任務加入 event 隊列。

  2. Future 中的 then 並無建立新的 Event 丟到 Event Queue 中,而只是一個普通的 Function Call ,

    FutureTask 執行完後,當即開始執行。

  3. 若是在 then() 調用以前 Future 就已經執行完畢了,那麼任務會被加入到 microtask 隊列中,而且該任務會執行 then() 中註冊的回調函數。

  4. 使用 Future.value 構造函數的時候,就會上一條同樣,建立 Task 丟到 microtask Queue 中執行 then 傳入的函數。

  5. Future.sync 構造函數執行了它傳入的函數以後,也會當即建立 Task 丟到 microtask Queue 中執行。

  6. 當任務須要延遲執行時,可使用 new Future.delay() 來將任務延遲執行。

scheduleMicrotask();

看一段代碼,而後再來分析它們的執行流程

//scheduleMicrotask
void testScheduleMicrotask() {
  //918346572
  scheduleMicrotask(() => print('s1'));

  new Future.delayed(new Duration(seconds: 1), () => print('s2'));

  new Future(() => print('s3')).then((_) {
    print('s4');
    scheduleMicrotask(() => print('s5'));
  }).then((_) => print('s6'));

  new Future(() => print('s10'))
      .then((_) => new Future(() => print('s11')))
      .then((_) => print('s12'));

  new Future(() => print('s7'));

  scheduleMicrotask(() => print('s8'));

  print('s9');
}
複製代碼

打印結果

s9
s1
s8
s3
s4
s6
s5
s10
s7
s11
s12
s2
複製代碼

這一次你們本身分析了 咳咳 ~ 。

注意

  1. 若是能夠,儘可能將任務放入event 隊列中。
  2. 使用 Future 的 then 方法或 whenComplete 方法來指定任務順序。
  3. 爲了保持你 app 的可響應性,儘可能不要將大計算量的任務放入這兩個隊列。
  4. 大計算量的任務放入額外的 isolate 中。

生成器

同步生成器

//同步生成器
//調用getSyncGenerator 馬上返回 Iterable
void main() {
  var it = getSyncGenerator(5).iterator;
  //  調用moveNext方法時getSyncGenerator纔開始執行
  while (it.moveNext()) {
    print(it.current);
  }
}

//同步生成器: 使用sync*,返回的是Iterable對象
Iterable<int> getSyncGenerator(int n) sync* {
  print('start');
  int k = n;
  while (k > 0) {
    //yield會返回moveNext爲true,並等待 moveNext 指令
    yield k--;
  }
  print('end');
}
複製代碼

注意

  1. 使用 sync* ,返回的是 Iterable 對象。
  2. yield 會返回 moveNext 爲 true ,並等待 moveNext 指令。
  3. 調用 getSyncGenerator 當即返回 Iterable 對象。
  4. 調用 moveNext 方法時 getSyncGenerator 纔開始執行。

異步生成器

//異步生成器調用
// getAsyncGenerator當即返回Stream,只有執行了listen,函數纔會開始執行
  StreamSubscription subscription = getAsyncGenerator(5).listen(null);
  subscription.onData((value) {
    print(value);
    if (value >= 2) {
      subscription.pause(); //可使用StreamSubscription對象對數據流進行控制
    }
  });

//異步生成器: 使用async*,返回的是Stream對象
Stream<int> getAsyncGenerator(int n) async* {
  print('start');
  int k = 0;
  while (k < n) {
    //yield不用暫停,數據以流的方式一次性推送,經過StreamSubscription進行控制
    yield k++;
  }
  print('end');
}
複製代碼

注意

  1. 使用 async* ,返回的是 Stream 對象。
  2. yield 不用暫停,數據以流的方式一次性推送,經過 StreamSubscription 進行控制。
  3. 調用 getAsyncGenerator 當即返回 Stream ,只有執行了 listen ,函數纔會開始執行。
  4. listen 返回一個 StreamSubscription 對象進行流監聽控制。
  5. 可使用 StreamSubscription 對象對數據流進行控制。

遞歸生成器

異步

void main(){
      //異步
  getAsyncRecursiveGenerator(5).listen((value) => print(value));
}

//異步遞歸生成器
Stream<int> getAsyncRecursiveGenerator(int n) async* {
  if (n > 0) {
    yield n;
    yield* getAsyncRecursiveGenerator(n - 1);
  }
}

複製代碼

同步

void main (){
  //遞歸生成器
  //同步
  var it1 = getSyncRecursiveGenerator(5).iterator;
  while (it1.moveNext()) {
    print(it1.current);
  }
}

//遞歸生成器:使用yield*
Iterable<int> getSyncRecursiveGenerator(int n) sync* {
  if (n > 0) {
    yield n;
    yield* getSyncRecursiveGenerator(n - 1);
  }
}
複製代碼

注意

  1. yield* 以指針的方式傳遞遞歸對象,而不是整個同步對象。

隔離

Isolates

現代的瀏覽器以及移動瀏覽器都運行在多核 CPU 系統上。 要充分利用這些 CPU,開發者通常使用共享內存 數據來保證多線程的正確執行。然而, 多線程共享數據一般會致使不少潛在的問題,並致使代碼運行出錯。

全部的 Dart 代碼在 isolates 中運行而不是線程。 每一個 isolate 都有本身的堆內存,而且確保每一個 isolate 的狀態都不能被其餘 isolate 訪問。

元數據

(註解)-@deprecated

main() {
  dynamic tv = new Television();
  tv.activate();
  tv.turnOn();
}

class Television {
  @deprecated
  void activate() {
    turnOn();
  }

  void turnOn() {
    print('Television turn on!');
  }
}

複製代碼

(註解)-@override

main() {
  dynamic tv = new Television();
  tv.activate();
  tv.turnOn();
  tv.turnOff();
}

class Television {
  @deprecated
  void activate() {
    turnOn();
  }
  
  void turnOn() {
    print('Television turn on!');
  }
  @override
  noSuchMethod(Invocation mirror) {
    print('沒有找到方法');
  }
}

複製代碼

注意

  1. 全部的 Dart 代碼均可以使用: @deprecated 和 @override。

(註解)-自定義

建立 todo.dart 文件

//todo.dart

class Todo {
  final String who;
  final String what;

  const Todo({this.who, this.what});
}

複製代碼
import 'todo.dart’; main() { dynamic tv = new Television(); tv.doSomething(); } class Television { @Todo(who: 'damon', what: 'create a new method') void doSomething() { print('doSomething'); } } 複製代碼

注意

  1. 在 java 中,若是自定義一個註解,須要添加 @Target 做用域註解,@Retention 註解類型註解,添加 @interface,而後定義註解參數。
  2. 構造方法定義爲編譯時常量

註釋

單行註釋

// 跟 Java 同樣
複製代碼

多行註釋

/**跟 Java 同樣**/
複製代碼

文檔註釋

/// 跟 Java 同樣
複製代碼

總結

三篇基礎 內容介紹了常見的 Dart 語言特性。 還有更多特性有待實現,可是新的特性不會破壞已有的代碼。 更多信息請參考 Dart 語言規範Effective Dart

要了解 Dart 核心庫的詳情,請參考 Dart 核心庫預覽

相關文章
相關標籤/搜索