Dart異步編程-future


Dart異步編程包含兩部分:Future和Stream

該篇文章中介紹Future

 

異步編程:Futures

  Dart是一個單線程編程語言。如果任何代碼阻塞線程執行都會導致程序卡死。異步編程防止出現阻塞操作。Dart使用Future對象表示異步操作。

介紹

  如下代碼可能導致程序卡死

  

// Synchronous code
printDailyNewsDigest() {
  String news = gatherNewsReports(); // Can take a while.
  print(news);
}

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

該程序獲取每日新聞然並打印,然后打印其他一系列用戶感興趣的信息:

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


該代碼存在問題printDailyNewsDigest讀取新聞是阻塞的,之后的代碼必須等待printDailyNewsDigest結束才能繼續執行。當用戶想知道自己是否中彩票,明天的天氣和誰贏得比賽,都必須等待printDailyNewsDigest讀取結束。

為了程序及時響應,Dart的作者使用異步編程模型處理可能耗時的函數。這個函數返回一個Future

 


什么是Future


Future表示在將來某時獲取一個值的方式。當一個返回Future的函數被調用的時候,做了兩件事情:
1. 函數把自己放入隊列和返回一個未完成的Future對象
2. 之后當值可用時,Future帶着值變成完成狀態。
為了獲得Future的值,有兩種方式:
1. 使用async和await
2. 使用Future的接口

 

Async和await


async和await關鍵字是Dart異步支持的一部分。他們允許你像寫同步代碼一樣寫異步代碼和不需要使用Future接口。

注:在Dart2中有輕微的改動。async函數執行時,不是立即掛起,而是要執行到函數內部的第一個await。在多數情況下,我們不會注意到這一改動

下面的代碼使用Async和await讀取新聞

 

// 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';

printDailyNewsDigest() async {
  String news = await gatherNewsReports();
  print(news);
}

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>';
Duration oneSecond = const Duration(seconds: 1);

final newsStream = new Stream.periodic(oneSecond, (_) => news);

// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;

// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future 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需要消耗一定時間的執行完成,而不會影響功能:用戶在讀取新聞之前獲得其他消息。

下面的圖展示代碼的執行流程。每一個數字對應着相應的步驟

1. 開始程序執行
2. main函數調用printDailyNewsDigest,因為它被標記為async,所有在該函數任何代碼被執行之前立即返回一個Future。
3. 剩下的打印執行。因為它們是同步的。所有只有當一個打印函數執行完成過后才能執行下一個打印函數。例如:中獎號碼在天氣預報執行打印。
4. 函數printDailyNewsDigest函數體開始執行
5. 在到達await之后,調用gatherNewsReports,程序暫停,等待gatherNewsReports返回的Future完成。
6. 當Future完成,printDailyNewsDigest繼續執行,打印新聞。
7. 當printDailyNewsDigest執行完成過后,最開始的Future返回完成,程序退出。

注:如果async函數沒有明確指定返回值,返回的null值的Future

錯誤處理

如果在Future返回函數發生錯誤,你可能想捕獲錯誤。Async函數可以用try-catch捕獲錯誤。

printDailyNewsDigest() async {
  try {
    String news = await gatherNewsReports();
    print(news);
  } catch (e) {
    // Handle error...
  }
}

try-catch在同步代碼和異步代碼的表現是一樣的:如果在try塊中間發生異常,那么在catch中的代碼執行

 

連續執行

你可以使用多個await表達式,保證一個await執行完成過后再執行下一個

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

expensiveB函數將等待expensiveA完成之后再執行。

 

Future API

在Dart1.9之前還沒有添加async和await時,你必須使用Future接口。你可能在一些老的代碼中間看到使用Future接口

為了使用Future接口寫異步代碼,你需要使用then()方法注冊一個回調。當Future完成時這個回調被調用。

下面的代碼使用Future接口:

// 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';

printDailyNewsDigest() {
  final future = gatherNewsReports();
  future.then((news) => print(news));
}

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>';
Duration oneSecond = const Duration(seconds: 1);

final newsStream = new Stream.periodic(oneSecond, (_) => news);

// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;

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


注意到printDailyNewsDigest被首先調用,但是新聞在最后被打印。這是因為代碼讀取內容是異步執行的。

程序執行流程:

1. 程序開始執行
2. main函數調用printDailyNewsDigest函數,在函數內部調用gatherNewsReports
3. gatherNewsReports收集新聞然后返回一個Future
4. printDailyNewsDigest使用then指定Future的響應函數。調用then()返回的新Future將使用then指定回調返回的值完成。
5. 剩下的打印函數同步執行,因為他們是同步的
6. 當所有新聞到達,gatherNewsReports返回的Future帶着一個包含新聞的字符串完成
7. 在printDailyNewsDigest中then函數指定的回調函數執行。打印新聞
8. 程序退出

在printDailyNewsDigest函數中then可以多種不同的方式寫。

 

1. 使用花括號。當有多個操作的時候,這種方式比較方便

printDailyNewsDigest() {
final future = gatherNewsReports();
future.then((news) {
  print(news);
  // Do something else...
});
}

2. 直接傳print函數

printDailyNewsDigest() =>
  gatherNewsReports().then(print);

 

 

錯誤處理

使用Future接口,你可以使用catchError捕獲錯誤

printDailyNewsDigest() =>
    gatherNewsReports()
        .then((news) => print(news))
        .catchError((e) => handleError(e));

如果新聞不可用,那么代碼的執行流程如下:
1. gatherNewsReports的Future帶一個Error完成。
2. then的Future帶一個Error完成。
3. catchError的回調處理錯誤,catchError的Future正常結束,錯誤不在被傳遞。

當使用Future接口時,在then后連接catchError,可以認為這一對接口相應於try-catch。

像then(), catchError返回一個帶回調返回值的新Future

 

使用then連接多個函數

當Future返回時需要順序執行多個函數是,可以串聯then調用

expensiveA()
    .then((aValue) => expensiveB())
    .then((bValue) => expensiveC())
    .then((cValue) => doSomethingWith(cValue));

使用Future.wait等待多個Future完成


當wait的所有Future完成時,Future.wait返回一個新的Future。這個Future帶了一個包含所有等待結果的列表。

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


當任意一個調用以error結束時,那么Future.wait也以一個Error結束。使用catchError處理錯誤

 

參考: https://www.dartlang.org/tutorials/language/futures

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM