(十)Dart Asynchrony(異步)养筒、Isolates

  • Dart 有一些語言特性來支持 異步編程试躏。 最常見的特性是 async 方法和 await 表達(dá)式。

  • Dart 庫中有很多返回 Future 或者 Stream 對(duì)象的方法棠枉。 這些方法是 異步的浓体, 這些函數(shù)在設(shè)置完基本的操作后就返回了, 而無需等待操作執(zhí)行完成辈讶。 例如讀取一個(gè)文件命浴,在打開文件后就返回了。

  • Future 和 JavaScript 中的 Promise 類似贱除,代表在將來某個(gè)時(shí)刻會(huì)返回一個(gè) 結(jié)果生闲。Stream 是一種用來獲取一些列數(shù)據(jù)的方式,例如 事件流月幌。 Future, Stream, 以及其他異步操作的類在 dart:async 庫中碍讯。

  • dart:async 庫在 web app 和命令行 app 都可以使用。 只需要導(dǎo)入 dart:async 即可使用:

import 'dart:async';

一扯躺、Future

有兩種方式可以使用 Future 對(duì)象中的 數(shù)據(jù):

  • 使用 asyncawait(要使用 await捉兴,其方法必須帶有 async 關(guān)鍵字)
  • 使用 Future API
  • 使用 await 的表達(dá)式比直接使用 Future api 的代碼要更加容易理解。

在 Dart 庫中隨處可見 Future 對(duì)象录语,通常異步函數(shù)返回的對(duì)象就是一個(gè) Future倍啥。 當(dāng)一個(gè) future 執(zhí)行完后,他里面的值 就可以使用了澎埠。

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // Do something.
  } else {
    // Do something else.
  }
}

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

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // 無法綁定到端口時(shí)作出反應(yīng)...
}

1.1.Declaring async functions(聲明異步方法)

async 方法: 是函數(shù)體被標(biāo)記為 async 的方法虽缕。 雖然異步方法的執(zhí)行可能需要一定時(shí)間,但是異步方法立刻返回 (在方法體還沒執(zhí)行之前就返回了)蒲稳。

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;

在一個(gè)方法上添加 async 關(guān)鍵字氮趋,則這個(gè)方法返回值為 Future。 例如江耀,下面是一個(gè)返回字符串 的同步方法:

String lookUpVersionSync() => '1.0.0';

如果使用 async 關(guān)鍵字凭峡,則該方法 返回一個(gè) Future,并且 認(rèn)為該函數(shù)是一個(gè)耗時(shí)的操作决记。

Future<String> lookUpVersion() async => '1.0.0';

注意:方法的函數(shù)體并不需要使用 Future API。 Dart 會(huì)自動(dòng)在需要的時(shí)候創(chuàng)建 Future 對(duì)象倍踪。

1.2.Using await expressions with Futures(使用 await 表達(dá)式)

await 表達(dá)式具有如下的形式:

await expression

在一個(gè)異步方法內(nèi)可以使用多次 await 表達(dá)式系宫。 例如索昂,下面的示例使用了三次 await 表達(dá)式 來執(zhí)行相關(guān)的功能:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await expression 中, expression 的返回值通常是一個(gè) Future扩借; 如果返回的值不是 Future椒惨,則 Dart 會(huì)自動(dòng)把該值放到 Future 中返回。 Future 對(duì)象代表返回一個(gè)對(duì)象的 promise 潮罪。 await expression 執(zhí)行的結(jié)果為這個(gè)返回的對(duì)象康谆。 await expression 會(huì)阻塞住,直到需要的對(duì)象返回為止嫉到。

如果 await 無法正常使用沃暗,請(qǐng)確保是在一個(gè) async 方法中。 例如要在 main() 方法中使用 await何恶, 則 main() 方法的函數(shù)體必須標(biāo)記為 async:

main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

1.3.Future 的API

使用 Future 的 then() 函數(shù)來 執(zhí)行三個(gè)異步方法孽锥, 每個(gè)方法執(zhí)行完后才繼續(xù)執(zhí)行后一個(gè)方法。

runUsingFuture() {
  //...
  findEntrypoint().then((entrypoint) {
    return runExecutable(entrypoint, args);
  }).then(flushThenExit);
}

下面是使用 await 表達(dá)式實(shí)現(xiàn)的同樣功能的代碼细层, 看起來更像是同步代碼惜辑,更加容易理解:

runUsingAsyncAwait() async {
  //...
  var entrypoint = await findEntrypoint();
  var exitCode = await runExecutable(entrypoint, args);
  await flushThenExit(exitCode);
}

可以使用 then() 來在 future 完成的時(shí)候執(zhí)行其他代碼。例如 HttpRequest.getString() 返回一個(gè)Future疫赎,由于 HTTP 請(qǐng)求是一個(gè) 耗時(shí)操作盛撑。使用then() 可以在 Future 完成的時(shí)候執(zhí)行其他代碼 來解析返回的數(shù)據(jù):

HttpRequest.getString(url).then((String result) {
  print(result);
});
// Should handle errors here.

使用 catchError() 來處理 Future 對(duì)象可能拋出的 各種異常和錯(cuò)誤:

HttpRequest.getString(url).then((String result) {
  print(result);
}).catchError((e) {
  // Handle or ignore the error.
});

then().catchError() 模式就是異步版本的 try-catch

重要: 確保是在 then() 返回的 Future 上調(diào)用 catchError()捧搞, 而不是在 原來的 Future 對(duì)象上調(diào)用抵卫。否則的話,catchError() 就只能處理原來 Future 對(duì)象拋出的異常而無法處理 then() 代碼 里面的異常实牡。

1.4.Chaining multiple asynchronous methods(鏈接多個(gè)異步方法)

then() 函數(shù)返回值為 Future陌僵,可以把多個(gè)異步調(diào)用給串聯(lián)起來。 如果 then() 函數(shù)注冊(cè)的回調(diào)函數(shù)也返回一個(gè) Future创坞,而 then() 返回一個(gè)同樣的 Future碗短。如果回調(diào)函數(shù)返回的是一個(gè)其他類型的值, 則 then() 會(huì)創(chuàng)建一個(gè)新的 Future 對(duì)象 并完成這個(gè) future题涨。

Future result = costlyQuery();

return result.then((value) => expensiveWork())
             .then((value) => lengthyComputation())
             .then((value) => print('done!'))
             .catchError((exception) => print('DOH!'));

上面的示例中偎谁,代碼是按照如下順序執(zhí)行的:

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

1.5.Waiting for multiple futures(等待多個(gè)future)

有時(shí)候,你的算法要求調(diào)用很多異步方法纲堵,并且等待 所有方法完成后再繼續(xù)執(zhí)行巡雨。使用 Future.wait()這個(gè)靜態(tài)函數(shù)來管理多個(gè) Future 并等待所有 Future 執(zhí)行完成。

Future deleteDone = deleteLotsOfFiles();
Future copyDone = copyLotsOfFiles();
Future checksumDone = checksumLotsOfOtherFiles();

Future.wait([deleteDone, copyDone, checksumDone])
    .then((List values) {
      print('Done with all the long steps');
    });

二席函、Stream

Stream 在 Dart API 中也經(jīng)常出現(xiàn)铐望,代表一些列數(shù)據(jù)。 例如, HTML 按鈕點(diǎn)擊事件就可以使用 stream 來表示正蛙。 還可以把讀取文件內(nèi)容當(dāng)做一個(gè) Stream督弓。

從 Stream 中獲取數(shù)據(jù)兩種方式:

  • 使用 async 和一個(gè) 異步 for 循環(huán) (await for)
  • 使用 Stream API

2.1.Using asynchronous for loops with Streams(在循環(huán)中使用異步)

異步 for 循環(huán)具有如下的形式:

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

上面 expression 返回的值必須是 Stream 類型的。 執(zhí)行流程如下:

  • 等待直到 stream 返回一個(gè)數(shù)據(jù)
  • 使用 stream 返回的參數(shù) 執(zhí)行 for 循環(huán)代碼乒验,
  • 重復(fù)執(zhí)行 1 和 2 直到 stream 數(shù)據(jù)返回完畢愚隧。
  • 使用 break 或者 return 語句可以 停止接收 stream 的數(shù)據(jù), 這樣就跳出了 for 循環(huán)并且 從 stream 上取消注冊(cè)了锻全。

如果異步 for 循環(huán)不能正常工作狂塘, 確保是在一個(gè) async 方法中使用。 例如鳄厌,要想在 main() 方法中使用異步 for 循環(huán)荞胡,則需要把 main() 方法的函數(shù)體標(biāo)記為 async:

main() async {
  ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  ...
}

2.2.Using an asynchronous for loop(for循環(huán)的異步使用)

有時(shí)候可以使用異步 for 循環(huán)(await for)來 替代 Stream API。

例如下面的示例中部翘,使用 Stream 的 listen() 函數(shù)來訂閱 一些文件硝训,然后使用一個(gè)方法參數(shù)來 搜索每個(gè)文件和目錄。

void main(List<String> arguments) {
  ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = new Directory(searchPath);
      startingDir
          .list(
              recursive: argResults[RECURSIVE],
              followLinks: argResults[FOLLOW_LINKS])
          .listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(new File(searchPath), searchTerms);
    }
  });
}

下面是使用 await 表達(dá)式和異步 for 循環(huán) 實(shí)現(xiàn)的等價(jià)的代碼新思, 看起來更像是同步代碼:

main(List<String> arguments) async {
  ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = new Directory(searchPath);
    await for (var entity in startingDir.list(
        recursive: argResults[RECURSIVE],
        followLinks: argResults[FOLLOW_LINKS])) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(new File(searchPath), searchTerms);
  }
}

注意: 在使用 await for 之前請(qǐng)確保使用之后的代碼看起來確實(shí)是 更加清晰易懂了窖梁。例如,對(duì)于 DOM時(shí)間監(jiān)聽器通常 不會(huì)使用 await for夹囚,原因在于 DOM 發(fā)送 無盡的事件流纵刘。 如果使用 await for 來在同一行注冊(cè)兩個(gè) DOM 事件監(jiān)聽器, 則第二個(gè)事件在不會(huì)被處理荸哟。

2.3.Listening for stream data(監(jiān)聽stream數(shù)據(jù))

要想在每個(gè)數(shù)據(jù)到達(dá)的時(shí)候就去處理假哎,則可以選擇使用 await for 或者 使用 listen() 函數(shù)來訂閱事件:

// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

上面的示例中, onClick 屬性是 “submitInfo” 按鈕提供的一個(gè) Stream 對(duì)象鞍历。

如果你只關(guān)心里面其中一個(gè)事件舵抹,則可以使用這些屬性: first, last, 或者 single。要想在處理事件之前先測(cè)試是否滿足條件劣砍,則 使用 firstWhere(), lastWhere(), 或者 singleWhere() 函數(shù)惧蛹。

如果你關(guān)心一部分事件,則可以使用 skip(), skipWhile(), take(), takeWhile(), 和 where() 這些函數(shù)刑枝。

2.4.Transforming stream data(轉(zhuǎn)換stream數(shù)據(jù))

經(jīng)常你需要先轉(zhuǎn)換 stream 里面的數(shù)據(jù)才能使用香嗓。 使用 transform() 函數(shù)可以生產(chǎn)另外一個(gè)數(shù)據(jù)類型 的 Stream 對(duì)象:

var stream = inputStream
    .transform(UTF8.decoder)
    .transform(new LineSplitter());

上面的代碼使用兩種轉(zhuǎn)換器(transformer)。第一個(gè)使用 UTF8.decoder 來把整數(shù)類型的數(shù)據(jù)流轉(zhuǎn)換為字符串類型的數(shù)據(jù)流装畅。然后使用 LineSplitter 把字符串類型數(shù)據(jù)流轉(zhuǎn)換為按行分割的數(shù)據(jù)流靠娱。 這些轉(zhuǎn)換器都來至于 dart:convert 庫。 參考 dart:convert ) 了解詳情掠兄。

2.5.Handling errors and completion(處理錯(cuò)誤和完善)

使用異步 for 循環(huán) (await for) 和使用 Stream API 的 異常處理情況是有 區(qū)別的像云。

如果是異步 for 循環(huán)锌雀,則可以 使用 try-catch 來處理異常。

在 stream 關(guān)閉后執(zhí)行的代碼位于異步 for 循環(huán) 之后苫费。

readFileAwaitFor() async {
  var config = new File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
      .transform(UTF8.decoder)
      .transform(new LineSplitter());
  try {
    await for (var line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

如果你使用 Stream API汤锨,則需要 使用 onError 函數(shù)來處理異常。 stream 完成后執(zhí)行的代碼要通過 onDone 函數(shù) 來執(zhí)行百框。

var config = new File('config.txt');
Stream<List<int>> inputStream = config.openRead();

inputStream
    .transform(UTF8.decoder)
    .transform(new LineSplitter())
    .listen((String line) {
      print('Got ${line.length} characters from stream');
    }, onDone: () {
      print('file is now closed');
    }, onError: (e) {
      print(e);
    });

2.6.More information(更多信息)

關(guān)于在命令行應(yīng)用中使用 Future 和 Stream 的更多示例,請(qǐng)參考 dart:io 里面的內(nèi)容牍汹。 下面也是一些可以參考的文章和教程:

三铐维、Isolates

現(xiàn)代的瀏覽器以及移動(dòng)瀏覽器都運(yùn)行在多核 CPU 系統(tǒng)上。 要充分利用這些 CPU慎菲,開發(fā)者一般使用共享內(nèi)存數(shù)據(jù)來保證多線程的正確執(zhí)行嫁蛇。然而, 多線程共享數(shù)據(jù)通常會(huì)導(dǎo)致很多潛在的問題露该,并導(dǎo)致代碼運(yùn)行出錯(cuò)睬棚。

所有的 Dart 代碼在 isolates 中運(yùn)行而不是線程。 每個(gè) isolate 都有自己的堆內(nèi)存解幼,并且確保每個(gè) isolate 的狀態(tài)都不能被其他 isolate 訪問抑党。

待續(xù)。撵摆。底靠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市特铝,隨后出現(xiàn)的幾起案子暑中,更是在濱河造成了極大的恐慌,老刑警劉巖鲫剿,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳄逾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灵莲,警方通過查閱死者的電腦和手機(jī)雕凹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笆呆,“玉大人请琳,你說我怎么就攤上這事≡唬” “怎么了俄精?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榕堰。 經(jīng)常有香客問我竖慧,道長(zhǎng)嫌套,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任圾旨,我火速辦了婚禮踱讨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砍的。我一直安慰自己痹筛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布廓鞠。 她就那樣靜靜地躺著帚稠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪床佳。 梳的紋絲不亂的頭發(fā)上滋早,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音砌们,去河邊找鬼杆麸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛浪感,可吹牛的內(nèi)容都是我干的昔头。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼篮撑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼减细!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赢笨,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤未蝌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茧妒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萧吠,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年桐筏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纸型。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梅忌,死狀恐怖狰腌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牧氮,我是刑警寧澤琼腔,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站踱葛,受9級(jí)特大地震影響丹莲,放射性物質(zhì)發(fā)生泄漏光坝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一甥材、第九天 我趴在偏房一處隱蔽的房頂上張望盯另。 院中可真熱鬧,春花似錦洲赵、人聲如沸鸳惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悲敷。三九已至,卻和暖如春俭令,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背部宿。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工抄腔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人理张。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓赫蛇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親雾叭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悟耘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容