Dart基础(九)-异步支持

1.简介:

  Dart库充满了返回Future或Stream对象的函数;这些函数都是异步的。这些函数在做一些可能耗时的操作(如I/O)时,会立即返回一个Future或Stream对象,进行函数后面代码的执行,而不需要等待该操作完成后再执行函数后面的代码。
  Dart中使用async``await关键字来实现异步编程,但是和Java、OC等异步不同,代码类似于同步代码,实际上是异步执行的。

关键术语:
同步操作:同步操作阻止其他操作执行,直到它完成。
同步函数:同步函数只执行同步操作。
异步操作:异步操作一旦启动,就允许其他操作在它完成之前执行。
异步函数:一个异步函数至少执行一个异步操作,也可以执行同步操作。

异步场景:

  • 获取网络数据(请求数据、图片下载、文件下载等);
  • 读取数据(从文件、数据库、磁盘中);
  • 写入数据(文件、数据库、磁盘等)。

2.处理Future

  Future表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的Future。该 Future会在结果准备好时通知调用者。
future与Future:
  future(小写“f”)是Future类的一个实例。future表示异步操作的结果,可以有两种状态:未完成uncompleted或已完成completed
未完成uncompleted
  当调用一个异步函数时,它将返回一个未完成的future。该future表示函数正在等待函数的异步操作完成或抛出错误。
成功的完成Completing with a value
  Future <T>类型的future以T类型的值结束。例如,Future <String>类型的future产生一个字符串值。如果future没有产生一个可用的值,那么future的类型是Future <void>
错误的完成Completing with an error
  如果函数执行的异步操作由于任何原因失败,则future函数将以错误的方式完成。

// 引入Future,并返回一个Future<void>的例子
Future<void> fetchUserOrder() {
  // Imagine that this function is fetching user info from another service or database.
  return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}

// 返回一个future错误的例子
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info but encounters a bug
  return Future.delayed(const Duration(seconds: 2),
      () => throw Exception('Logout failed: user ID is invalid'));
}

Futuer和future总结:

  • Future<T>实例产生类型T的值;
  • 如果future没有产生一个可用的值,那么future的类型是future <void>
  • future有两种状态:未完成或已完成;
  • 当调用一个返回future的函数时,该函数将会进入一个异步任务队列中queues,并立即返回一个未完成状态的future
  • future的操作完成时,它会返回一个T类型的值或者一个错误。

3.异步async和等待await

  asyncawait关键字提供了一种声明性的方式来定义异步函数并使用它们的结果;使用的基本原则:

  • 要定义一个异步函数,请在函数体前添加async
  • await关键字只在异步函数中有效;
  • 同步函数中调用异步函数,处理异步函数结果需要.then语法。
Future<String> createOrderMessage() async {
  print("message star");
  var order = await fetchUserOrder3();
  print("message return");
  return 'Your order is: $order';
}

Future<String> fetchUserOrder3() async {
  // Imagine that this function is more complex and slow.
  print("order star");
  var result = await Future.delayed(
    const Duration(seconds: 2), () {
    return 'Large Latte';
  });
  print("order return");
  return result;
}

// 同步函数中调用
print('message top');
createOrderMessage().then((value) {
  print(value);
});
print('message bottom');

// 打印顺序
flutter: message top
flutter: message star
flutter: order star
flutter: message bottom
flutter: order return
flutter: message return
flutter: Your order is: Large Latte

  如何解读上述代码的打印顺序呢?

  • 同步函数中代码是从上往下顺序执行的;
  • 异步函数中,可能是全部同步执行,也可能有同步和也有异步执行;
  • 异步函数中,以await关键字为分界线,await以上包含await调用函数的语句是同步的,await的返回结果和后面的代码是异步执行。
    示例如图,红色的代码为同步执行,绿色代码为异步执行的:
    异步函数的执行.png

      开始解读:
  1. 同步函数首先打印:message top;
  2. 同步调用异步函数createOrderMessage,进入该函数;
  3. 在函数createOrderMessage中,首先同步打印:message star;
  4. 同步调用await fetchUserOrder3(); ,进入函数fetchUserOrder3;
  5. 在函数fetchUserOrder3中,首先同步打印:order star;
  6. 同步调用await Future.delayed ,进行函数Future.delayed调用;
  7. 函数Future.delayed延迟执行,为异步调用,直接返回一个Future<String>的实例future,并加入异步队列中等待执行;
  8. 函数fetchUserOrder3检测到异步执行,直接返回一个future,退出同步执行,将异步部分加入异步队列中等待执行;
  9. 函数createOrderMessage与6相同;
  10. 同步函数中异步函数createOrderMessage的同步部分执行完毕,继续执行下面同步代码,打印:message bottom;
  11. 队列要求先进先出,当最先加入异步队列的任务已经完成后,会以加入的次序执行后续加入的异步代码,后面的打印书序为:
    order return
    message return
    Your order is: Large Latte

3.异步错误error处理:

  在异步函数中,如同同步函数一样使用try-catch处理出现的错误或者异常。

Future<void> printOrderMessage() async {
  try {
    print('Awaiting user order...');
    var order = await fetchUserOrder();
    print(order);
  } catch (err) {
    print('Caught error: $err');
  }
}

Future<String> fetchUserOrder() {
  // Imagine that this function is more complex.
  var str = Future.delayed(
      const Duration(seconds: 4),
      () => throw 'Cannot locate user order');
  return str;
}

Future<void> main() async {
  // 会抛出Cannot locate user order异常
  await printOrderMessage();
}

4.处理流Streams

官方文档
  Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable,不同的是当你向 Iterable 获取下一个事件时它会立即给你,但是 Stream 则不会立即给你而是在它准备好时告诉你。
  Stream概要:

  • Stream提供一个异步的数据序列。
  • 数据序列包括用户生成的事件和从文件读取的数据。
  • 你可以使用Stream API中的listen()方法和await for关键字来处理一个Stream
  • 当出现错误时,Stream提供一种处理错误的方式。
  • Stream 有两种类型:Single-SubscriptionBroadcast
4.1接收Stream事件:

  Stream可以通过许多方式创建,而这些所有的创建方式都可以相同的方式在代码中使用:像使用for循环 迭代一个Iterable一样,我们可以使用 异步for循环 (通常我们直接称之为await for)来迭代Stream中的事件。例如:

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (final value in stream) {
    sum += value;
  }
  return sum;
}

  上面代码只是简单地接收整型事件流中的每一个事件并将它们相加,然后返回(被Future包裹)相加后的整型值。当循环体结束时,函数会暂停直到下一个事件到达或Stream完成。内部使用await for循环的函数需要使用async关键字标记。
下面的示例中使用了async*函数生成一个简单的整型Stream来测试上一个代码片段:

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (final value in stream) {
    sum += value;
  }
  return sum;
}

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i;
  }
}

// 调用
var stream = countStream(10);
var sum = sumStream(stream);
sum.then((value) {
  print(value);
});
4.2Stream错误处理:

  当Stream再也没有需要处理的事件时会变为完成状态。与此同时,调用者可以像接收到新事件回调那样接收Stream完成的事件回调。当使用await for循环读取事件时,循环会在Stream完成时停止。

  有时在Stream完成前会出现错误;比如从远程服务器获取文件时出现网络请求失败,或者创建事件时出现 bug;在出现错误时,需要将错误告知使用者。

  Stream可以像提供数据事件那样提供错误事件。大多数Stream会在第一次错误出现后停止,但其也可以提供多次错误并可以在在出现错误后继续提供数据事件。在本篇文档中我们只讨论Stream最多出现并提供一次错误事件的情况。

  当使用await for读取Stream时,如果出现错误,则由循环语句抛出,同时循环结束。你可以使用try-catch语句捕获错误。下面的示例会在循环迭代到参数值等于 4 时抛出一个错误:

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    if (i == 4) {
      throw Exception('Intentional exception');
    } else {
      yield i;
    }
  }
}

5.隔离区Isolates

  大多数计算机中,甚至在移动平台上,都在使用多核 CPU。为了有效利用多核性能,开发者一般使用共享内存的方式让线程并发地运行。然而,多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

  为了解决多线程带来的并发问题,Dart 使用isolate替代线程,所有的 Dart 代码均运行在一个 isolate 中。每一个isolate有它自己的堆内存以确保其状态不被其它 isolate 访问。

  所有的 Dart 代码都是在一个isolate中运行,而非线程。每个 isolate 都有一个单独的执行线程,并且不与其他的isolate共享任何可变对象。

你可以查阅下面的文档获取更多相关信息:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容