Flutter異步編程: Futures

重點

  • Dart代碼運行在一個已執行的線程內。
  • 阻塞線程執行的代碼可以使程序凍結。
  • Future對象(Futures)表示異步執行的結果(將要完成的執行結果或I/O操做)。
  • 異步函數使用await()(或者用then()),暫停執行直到future完成。
  • 異步函數使用try-cache表達式,捕獲錯誤。
  • 構造一個isolate(web 使用 worker),馬上運行代碼,

Dart代碼運行在一個已執行的線程內。若是Dart代碼阻塞,例如,運行一個長時間運算或者等待I/O操做,程序將要凍結。 異步操做可讓你在等待一個操做完成時,進行其餘工做。Dart使用Future對象(Futures)表示異步執行結果。 你可使用async和await也可使用Future API,進行futures開發。html

Node: 全部Dart代碼運行在isolate上下文中,這個isolate擁有Dart代碼全部內存。當Dart代碼執行時,isolate內不能運行其餘代碼。 若是你想多部分Dart代碼同時運行,你能運行他們在其餘的isolate(Web 用workers代替 isolate)。多個isolate同時運行,一般運行在各自的CPU內核上。isolate不共享內存,他們之間的惟一溝通方式是發送消息。關於isolate更多內容,請查看文檔isolates或者web workersweb

介紹

讓咱們看一些可能致使程序凍結的代碼:編程

// Synchronous code
void printDailyNewsDigest() {
  var newsDigest = gatherNewsReports(); // Can take a while.
  print(newsDigest);
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}
複製代碼

咱們的程序收集當天的新聞,並打印它,而後打印一些用戶感興趣的其餘項目:api

<gathered news goes here>
Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0 複製代碼

咱們的代碼是有潛在問題的:因爲gatherNewsReports()阻塞,餘下的代碼只能一直等待gatherNewsReports()從文件讀取返回值以後執行。若是讀取文件花費很長時間,用戶必須等,儘管用戶可能更想知道他們是否贏了彩票,明天的天氣是什麼,誰贏了今天的比賽。bash

爲了保證程序響應,Dart庫做者處理耗時操做時使用異步模式,這些函數使用future做爲返回值。異步

#Future 是什麼? future是一個Future對象,它表示一個異步操做產生一個T類型的結果。若是結果爲不可用的值,返回類型爲Future。當一個返回futrue的函數被調用時,有兩件事發生:async

  1. 該函數將加入工做隊列,並返回一個未完成的Future對象。
  2. 以後,當操做完成後,返回完成的Future對象值或者錯誤。

使用future,有兩種方式:ide

  • 用async 和 await
  • 用Future API

Async 和 await

async和await是Dart支持異步編程的一部分。他們容許你寫異步代碼,看起來像同步代碼而且不須要使用Future API。 異步函數便是將async關鍵字放在函數體前便可。await關鍵字只用在async函數。 下面程序使用async和await模擬從 www.dartlang.org. 讀取內容:異步編程

// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

Future<void> printDailyNewsDigest() async {
  var newsDigest = await gatherNewsReports();
  print(newsDigest);
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}

printWinningLotteryNumbers() {
  print('Winning lotto numbers: [23, 63, 87, 26, 2]');
}

printWeatherForecast() {
  print("Tomorrow's forecast: 70F, sunny.");
}

printBaseballScore() {
  print('Baseball score: Red Sox 10, Yankees 0');
}

const news = '<gathered news goes here>';
const oneSecond = Duration(seconds: 1);

// Imagine that this function is more complex and slow. :)
Future<String> gatherNewsReports() =>
    Future.delayed(oneSecond, () => news);

// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future<String> gatherNewsReportsFromServer() => HttpRequest.getString(
//      'https://www.dartlang.org/f/dailyNewsDigest.txt',
//    );
複製代碼

運行結果:函數

Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0 <gathered news goes here> 複製代碼

printDailyNewsDigest()是第一個被調用的,雖然只是輸出一行,可是新聞也是最後被打印的。這是由於運行和打印代碼是異步運行的。

在這個例子中,printDailyNewsDigest()調用非阻塞gatherNewsReports(),調用gatherNewsReports()會將執行任務加入隊列,但不會阻止剩下代碼執行。程序打印彩票號碼,預測棒球比賽分數。當gatherNewsReports() 完成得到新聞後,打印它。若是gatherNewsReports()花費一些時間完成,因爲異步執行也不會給用戶帶來很大的影響。在打印每日新聞以前用戶能夠閱讀其餘消息。

請留意返回類型, gatherNewsReports()的返回類型是Future,這意味這個返回值是一個以字符future。printDailyNewsDigest() 無返回值,它的返回類型爲Future。

下圖展現調用流程,數字和步驟相互對應:

  1. 程序開始運行
  2. main函數調用printDailyNewsDigest(),printDailyNewsDigest()開始執行。
  3. printDailyNewsDigest()使用await調用atherNewsReports(),atherNewsReports()開始執行。
  4. gatherNewsReports()返回一個未完成的future(一個Future實例)。
  5. 由於printDailyNewsDigest()是一個異步函數而且等待返回值,它暫停執行並返回一個未完成的future(這種狀況下,返回的是Future)給它的調用者(這裏是main函數)。
  6. 執行剩下的打印函數。由於他們是同步的,每一個函數徹底執行完,纔會執行下一個函數。例如,彩票中獎號碼都是在天氣預報以前打印出來的。
  7. 當main()函數完成執行,異步函數仍然能執行。首先gatherNewsReports() 完成後返回future,而後 printDailyNewsDigest()繼續執行,打印新聞。
  8. 當printDailyNewsDigest()函數體完成執行,完成的future返回,程序退出。

注意,異步函數當即(同步地)開始執行。當第一次出現一下狀況時,函數暫停執行,並返回一個未完成的future:

  • 函數的第一個await表達式(函數得到未完成future以後)。
  • reture 語句。
  • 函數體結束。

錯誤處理

異步函數使用try-cache進行錯誤處理。

Future<void> printDailyNewsDigest() async {
  try {
    var newsDigest = await gatherNewsReports();
    print(newsDigest);
  } catch (e) {
    // Handle error...
  }
}
複製代碼

try-cache的行爲在異步代碼和同步代碼中是相同的,若是try塊中的代碼拋出異常,則catch子句中的代碼將執行。

順序處理

您可使用多個await表達式來確保順序執行,即每一個語句在執行下一個語句以前完成:

// Sequential processing using async and await.
main() async {
  await expensiveA();
  await expensiveB();
  doSomethingWith(await expensiveC());
}
複製代碼

expensiveA()執行完成後,纔會執行expensiveB().

#Future API 在Dart 1.9中添加async和await以前,您必須使用Future API。目前仍然可能在老的Dart代碼中以及須要比asyn-await提供更豐富功能的代碼中,看到Future API。

使用Future API寫異步代碼,用then()註冊回調。當Future完成後,回調被執行。

下面程序使用Future API模擬從 www.dartlang.org. 讀取內容:

// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then(print);
  // You don't *have* to return the future here. // But if you don't, callers can't await it. } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 複製代碼

結果輸出:

Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0 <gathered news goes here> 複製代碼

printDailyNewsDigest()是第一個被調用的,雖然只是輸出一行,可是新聞也是最後被打印的。這是由於運行和打印代碼是異步運行的。

程序執行步驟:

  1. 開始執行
  2. main()函數調用printDailyNewsDigest(),printDailyNewsDigest()沒有當即返回,而是調用了gatherNewsReports().
  3. gatherNewsReports()開始獲取新聞而且返回一個Future。
  4. printDailyNewsDigest()使用then()處理對應的Future返回值。調用then()返回一個新的Future,它將做爲then()的回調參數。
  5. 執行剩下的打印函數。由於他們是同步的,每一個函數徹底執行完,纔會執行下一個函數。例如,彩票中獎號碼都是在天氣預報以前打印出來的。
  6. 當全部的新聞都收到後, gatherNewsReports()完成後返回包含新聞信息字符串的Future。
  7. 在printDailyNewsDigest()中指定的then()執行,打印新聞。
  8. 退出程序。

Node : 在printDailyNewsDigest()函數,future.then(print) 等價於: future.then((newsDigest) => print(newsDigest))

另外,then()內部代碼可使用{}:

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then((newsDigest) {
    print(newsDigest);
    // Do something else...
  });
}
複製代碼

你須要提供一個參數給then()的回調,即便Future是Future類型。按照慣例,一個無用參數使用下劃線表示。

final future = printDailyNewsDigest();
return future.then((_) {
  // Code that doesn't use the `_` parameter... print('All reports printed.'); }); 複製代碼

##錯誤處理 使用Future API,你能用catchError()捕獲錯誤。

Future<void> printDailyNewsDigest() =>
    gatherNewsReports().then(print).catchError(handleError);

複製代碼

若是新聞數據讀取無效,代碼的執行流程以下:

  1. gatherNewsReports()返回包含錯誤信息的future。
  2. then()返回的future以錯誤結束,print()函數不被掉用。
  3. catchError() (handleError())處理錯誤, catchError()返回的正常的future,而且錯誤不會被傳播。

鏈式模式是Future API的常見模式。能夠將Future API 等同於try-catch模塊。

與then()相似,catchError()返回一個新的Future,它是回調的返回值。 更多詳細信息和例子,請參考Futures and Error Handling.

調用多個函數

考慮三個函數,expensiveA(), expensiveB(), 和expensiveC(),這三個函數都返回Future對象。你能順序調用他們,或者你能同時開始他們,並在全部函數執行完成後作一些事情。Future接口能夠輕鬆的處理這兩種用例。

使用then()進行鏈式調用

當函數須要按順序執行時,使用鏈式then():

expensiveA()
    .then((aValue) => expensiveB())
    .then((bValue) => expensiveC())
    .then((cValue) => doSomethingWith(cValue));
複製代碼

嵌套回調雖然能夠工做,可是難於閱讀。

使用Future.wait()等待多個future完成

若是函數的執行順序不重要,你能夠用Future.wait()。

當你傳遞一個future列表給Future.wait(),它馬上返回一個未完成的future。直到給定的future列表所有執行完這個future才完成。future的返回值,由列表中的每一個future的返回值組成。

Future.wait([expensiveA(), expensiveB(), expensiveC()])
    .then((List responses) => chooseBestResponse(responses, moreInfo))
    .catchError(handleError);

複製代碼

若是任何一個函數返回錯誤,Future.wait()的futrue都以錯誤結束。使用catchError()處理錯誤。

#其餘資源 閱讀如下文檔,瞭解有關在Dart中使用future和異步編程的更多詳細信息:

#原文連接 Asynchronous Programming: Futures

相關文章
相關標籤/搜索