当前位置: 首页 > 知识库问答 >
问题:

async/await/then如何在Dart中真正工作?

夏侯朝斑
2023-03-14

这可能是一个反复出现的问题,但我发现了相互矛盾的答案,现在我不知道哪一个是正确的。我以为我理解了这个概念,然后我开始阅读所有这些答案,我完全困惑了,所以我在寻找一个明确而简单的答案来回答我的问题,我很容易理解。

根据这个答案和这篇文章,wait应该中断代码执行,实际上等待未来完成,然后继续按顺序执行其余代码。它还表明这可能会阻塞主线程,这在这种情况下是合乎逻辑的。

另一方面,来自flutter团队的这个、这个和这个视频表明wait不会阻止其余的代码执行,注册回调以在未来完成时执行只是语法糖,这是然后做的同样的事情。

现在,我试着编写一个小程序来理解其中哪一个是正确的,似乎第一种方法就是要走的路:

import 'dart:async';

// prints: 
// 1000+
// 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds); 
  
}

反对:

import 'dart:async';

// prints:
// 0
// 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds);
  
}

所以我只想知道为什么Flatter团队和一些人建议,await不会阻止代码执行,以及这个概念是如何工作的。

共有3个答案

虞博涛
2023-03-14

实际上你的两个函数都有相同的结果,让我更清楚地解释一下。。

当调用异步函数时,它们只是不阻塞要呈现的应用程序的另一部分。我们在内部执行的任何操作都将被延迟,但其余部分将按原样工作。

现在让我们来看看你的例子

import 'dart:async';

// prints 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1));
  
  print(watch.elapsedMilliseconds); 
  
}

在上面的示例中,您只是传递延迟持续时间,而不是回调。因此,它将该部分的其余部分视为回调,一旦持续时间完成,就会调用该回调。现在你在做什么,你告诉你的函数等待一段时间,你已经提供了execute进一步的代码。

所以结果是1000

在下面的例子中

import 'dart:async';

// prints:
// 0
// 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);}); // prints 1000+
  
  print(watch.elapsedMilliseconds); // prints 0
  
}

您正确地为Future分配了一个回调。所以现在,未来将只保留它的回调,让其余部分完成。

这就是它先打印0,然后在延迟一秒钟后打印1000的原因。

未来和未来的延迟有不同的工作流程,这可能是现在使用等待或不等待的正确方式。

东方镜
2023-03-14

安德烈亚的回答在技术上是正确的。然而,在我能够理解它到底是如何工作的之前,我仍然需要对它进行很多思考,这就是为什么我会尝试为任何可能有相同问题的人简化事情。

假设你有一个飞镖计划;显然是使用main()。我们的程序调用两个函数foo()bar()

函数foo()做一些异步工作,例如网络调用:

Future<void> foo() async{
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1));
  
  print(watch.elapsedMilliseconds);
}

函数bar()是执行一些同步代码的正常函数:

void bar() {
  print("Some synchronous code");
}

现在,假设您的main()如下所示:

void main() {
  foo();
  bar();
}

主程序启动并调用foo()-在main()中没有wait-我们点击foo()中的wait程序说:“哦!我不应该延迟main()中的其余执行。当异步工作完成时,我必须注册一个要执行的回调,然后继续执行main()"。foo()从调用堆栈中弹出,然后调用bar()并打印“一些同步工作”,也从调用堆栈中弹出。与此同时,foo()中的异步工作完成并发出完成信号。这会被事件循环拾取,该事件循环返回执行foo()中的其余代码(或者如果我们使用. then()则回调中的代码);当然,如果主线程不忙。

简单地说就是发生了什么。正如Andrija所建议的,await在同一个函数中阻止其余代码的执行;程序的其余部分将正常运行。如果我们使用main()中的wait来等待foo(),那么main()中的执行也会被阻止,直到foo()中的异步工作完成,这不是我最初所想的。

我的想法是,main()中的代码也会根据foo()中的await延迟,这与我们之前看到的情况不同。

朱通
2023-03-14

我认为对阻塞有一点误解。当您查看第一个示例时——wait只会阻止函数中的其余代码执行。您的应用程序的其余部分仍然可以正常工作。

您需要理解一件事:异步/等待语法只是一种语法糖分。然后是(回调)语法。它们都实现了相同的功能,只有async/await更容易阅读、调试和理解。正如你所看到的,在你的两个例子中,你得到的结果是相同的。问题是:你更喜欢哪种语法?

为了澄清这一点,我们假设您想引入几个1秒的等待事件,并在每一个事件之后写一条消息。

您的第一个示例如下所示:

import 'dart:async';

// prints 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  
}

请注意阅读代码和理解是多么容易。

现在,对第二个示例进行更改以实现同样的事情:

import 'dart:async';

void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){
    print(watch.elapsedMilliseconds);
    Future.delayed(Duration(seconds:1)).then((_){
        print(watch.elapsedMilliseconds);
        Future.delayed(Duration(seconds:1)).then((_){
             print(watch.elapsedMilliseconds);
        });
    });
  });
}

它们都能达到同样的效果——但第二个例子会让你的眼睛受伤。

您需要考虑的另一个有趣的场景是——如果您希望同时发生几件事情怎么办?这并不罕见——如果您需要从3个不同的服务器获取3个图像,您不会按顺序获取它们。您希望同时触发所有3个请求,并等待它们全部完成。

使用async/await非常简单:

import 'dart:async';

// prints 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  var f1 = Future.delayed(Duration(seconds:1));
  var f2 = Future.delayed(Duration(seconds:2));
  var f3 = Future.delayed(Duration(seconds:3));

  await Future.wait([f1, f2, f3]);

  print(watch.elapsedMilliseconds); 

  
}

请注意,因为我们没有在每个Future.delayed前面放wait,这意味着我们将开始延迟的未来,但我们不会等待它的完成。

您将看到整个功能只需3秒钟即可完成;因为这三个计时器同时运行。将来等待将等待一个期货列表完成。

现在——很明显,你并不真的需要。then()语法在大多数情况下都适用,但我认为它仍然适用于更复杂的场景。

例如:您需要从3台服务器获取3个映像。这些服务器中的每台都有一个备份服务器;如果第一台服务器结果返回null-您需要从备份服务器获取资源。另外:如果备份服务器1或备份服务器2返回null,您需要调用服务器4来获取单个映像。

你甚至可以画一个小图表来描述这一点。这就是我要说的。然后()语法就派上了用场——我们仍然会将它与async/await结合起来。我认为,一旦你完全理解了这个例子,你就会非常理解async/await和。然后()。走吧:

import 'dart:async';
import 'dart:math';

Future<int?> getImage(String server) async {
  var rng = Random();
  
  print("Downloading from $server");
  
  // we'll add random delay to simulate network
  await Future.delayed(Duration(seconds: rng.nextInt(5)));
  
  print("$server is done");
  
  // high chance of returning null
  if (rng.nextInt(10)<7) return null;
  return 1;
}

// prints 1000+
void main() async {
  
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  // get the image from server 1
  var f1 = getImage("Server 1").then((data) async { 
     return data ?? await getImage("Server 1 backup");
  });
  
  var f2 = getImage("Server 2").then((data) async { 
     return data ?? await getImage("Server 2 backup");
  });

  var f4=Future.wait([f1, f2]).then((data) async {
    if (data[0]==null || data[1]==null) {
       return [await getImage("Server 4")];
    } else {
       return data;
    }
  });
  
  var f3 = getImage("Server 3").then((data) async { 
     return data ?? await getImage("Server 3 backup");
  });

  await Future.wait([f3, f4]);

  print("elapsed ${watch.elapsedMilliseconds} ms"); 
  
}

这里有一个新东西是:。然后()将返回一个未来的对象,您仍然可以使用wait关键字等待它。告诉过你这是同一件事。。。。

没有然后()语法,您需要再创建一个异步函数来处理这个问题,这会使代码有点复杂,更难阅读。具有then()语法代码更易于管理。再次看到-。那么()和async/await实际上是一回事。。。

当事情是线性的时,标准的async/await会有所帮助(比如我展示的multiple Future.delayed Example)。但当您看到一个复杂的场景时,可以通过多个分支并行运行的图来描述-。然后()就会派上用场。

如果Dart是单线程的,可以这样想:您的代码在Dart引擎(或Dart VM)中运行,而这段代码实际上是单线程的。但对外部世界的任何调用都将并行运行(调用远程服务器,甚至调用本地硬盘驱动器,调用同一主机上的其他进程,如操作系统——是的,甚至调用我的示例中的计时器)。

就像我上面的例子:我调用了3个远程服务器来获取一些东西,我链接了3个不同的回调,每次调用1个。而“外部世界的事情”——称为服务器——实际上是并行发生的。Dart的单线程简单地保证在任何给定时间点只执行我的代码的一行。

如果您来自Java背景,您就会知道Java同步多个线程有多难:这是代码经常中断的地方。在Dart中,您无需担心这一点。真正的性能优化是Dart VM之外发生的任何事情都真正并行运行——Dart会为您处理它。

现在这是如何工作的:事件循环。这是一个小飞镖引擎,可以跟踪您的所有远程服务器调用,并在准备就绪时回调您的-嗯,回调过程。事件循环负责您的代码一次处理一个请求......

 类似资料:
  • 我读到过,在中包含异步是async/await的反模式。下面的代码可以工作,但是我想知道如果没有中的,如何实现相同的结果。 谢了。

  • Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。 Async function 让我们以 async 这个关键字开始。它可以被放置在一个函数前面,如下所示: async function f() { return 1; } 在函数前面的 “async” 这个单词表达了一个简单的事情:即这个函数总是返回一个 promise。其他值将自动被

  • 在第一章节,我们简要介绍了async/.await,并用它来构建一个简单的服务器。本章将更为详细讨论async/.await的它如何工作以及如何async代码与传统的 Rust 程序不同。 async/.await是 Rust 语法的特殊部分,它使得可以 yield 对当前线程的控制而不是阻塞,从而允许在等待操作完成时,其他代码可以运行。 async有两种主要的使用方式:async fn和asyn

  • 用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。 为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。 请注意,async和await是针对coroutin

  • 我已经复习了/,在复习了几篇文章之后,我决定自己测试一下。然而,我似乎无法理解为什么这不起作用: 控制台输出以下内容(节点V8.6.0): >外:“对象承诺” >内部:嘿那里 为什么函数内部的日志消息会在之后执行?我认为创建/的原因是为了使用异步任务执行同步执行。 有没有一种方法可以使用函数内部返回的值,而不用后面的?