【问题标题】:What are possible outputs of this dart code此飞镖代码的可能输出是什么
【发布时间】:2021-12-06 01:35:09
【问题描述】:

考虑以下 Dart 代码:

main() {
  
  var myFuture = Future(
      (){
        print("Executing Future"); 
        return 10;
      }
  );
  print("Exiting main now");
}

它产生以下输出:

Exiting main now
Executing Future

我对 Dart 代码执行的理解是应用程序在一个带有单个线程的隔离中执行(称为事件循环)。此事件循环在事件到达时按顺序处理事件。上面代码中Future的创建只是将一个新事件放入事件管道中,事件循环在处理完当前事件(也就是main的执行)后,会拾取该事件进行处理。

这意味着输出中的行顺序绝对不可能颠倒。我的意思是,保证 100% 上述程序永远不会打印这个:

Executing Future
Exiting main now

我的理解正确吗?

【问题讨论】:

    标签: dart


    【解决方案1】:

    一般来说,您不应该编写这样的代码,因为您依赖于异步事件的执行顺序。如果你依赖某件事发生,你应该等待依赖的未来来获取值。

    原因是您的代码确实按照您描述的顺序执行。但是我们也可以创建一个Future.sync,它会改变同一个程序的执行方式(根据构造函数的名称,这并不奇怪):

    void main() {
      final myFuture = Future.sync(() {
        print("Executing Future");
        return 10;
      });
      print("Exiting main now");
    }
    
    // Returns:
    // Executing Future
    // Exiting main now
    

    在将Future 返回给调用代码之前,您无法真正看到正在执行多少代码。这就是为什么在异步代码方面除了等待所需的期货/流来获取值之外,您永远不应该依赖执行顺序的主要原因。

    但是,是的,如果这只是一个理论上的问题,那么根据 Future 构造函数的 API 描述,您的具体代码将始终按照您所说的那样执行:

    创建一个包含使用 Timer.run 异步调用计算结果的未来。

    https://api.dart.dev/stable/2.14.4/dart-async/Future/Future.html

    我一般可以推荐以下关于 Dart 中的事件循环的文章:

    https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a

    更新 1

    我这么说的原因:

    在将 Future 返回给调用代码之前,您无法真正看到正在执行多少代码。

    是因为我们可以这样做:

    import 'dart:async';
    
    void main() {
      print('Step 1');
      myAsyncMethod().then(print);
      print('Step 3');
    }
    
    Future<String> myAsyncMethod() async {
      print('Step 2');
      await Future.delayed(const Duration(milliseconds: 1), () => null);
      return 'Step 4';
    }
    

    将输出:

    Step 1
    Step 2
    Step 3
    Step 4
    

    出现这种行为的原因是,async 方法将作为同步方法运行,直到第一个 await 操作之后它会像异步函数一样运行。因此,当我们调用myAsyncMethod 时,我们会立即执行print('Step 2'),但随后我们会返回并执行print('Step 3')

    所以如果我们只看:

    myAsyncMethod().then(print);
    

    在获得Future 之前,我们并不真正知道执行了什么操作。还应该注意,这不是async 标记方法所独有的。我们还可以执行以下操作,返回相同的输出:

    import 'dart:async';
    
    void main() {
      print('Step 1');
      myAsyncMethod().then(print);
      print('Step 3');
    }
    
    Future<String> myAsyncMethod() {
      final completer = Completer<String>();
      print('Step 2');
      Future.delayed(
        const Duration(milliseconds: 1),
        () => completer.complete('Step 4'),
      );
      return completer.future;
    }
    

    【讨论】:

    • 是的,这是一个理论问题。但我不同意你关于不依赖于订单的评论。 Dart 保证隔离中的代码由称为事件循环的单个线程执行,对吗?所以,上面的顺序是有保证的。正确的?如果它得到保证,那么它是可靠的,即使它没有太多用处。
    • 另外,你的意思是,“在 Future 返回到你的调用代码之前,你真的看不到正在执行多少代码?” main 中的每一行代码都将在 Future 完成之前执行。这就是只有一个线程(事件循环)执行所有代码这一事实的直接含义。因此,如果它当前正在执行 main 中的代码,它就无法完成 Future。正确的?只有main中的代码结束后,才会执行Future中的代码。
    • @Priyshrm 我在我的答案中添加了一个“更新 1”部分,我试图解释我的“在 Future 返回到您的调用代码之前,您无法真正看到正在执行多少代码?”评论。我希望添加的解释有助于更好地理解该部分。
    • 好的,我明白了,是的,这是您展示的一个非常棘手的代码(您的第一个示例)。这真是一团糟。我希望在第 3 步之前打印第 4 步。我需要一些时间才能理解您的第二个示例。非常感谢您的帮助!
    • @Priyshrm Completer 是一种生成Future 的方法,我们可以立即返回,然后用一个值填充它。 Dart 中的Future 实际上只是表示一个变量,它可能在未来获得一个值,并且您可以订阅该事件。所以我在第二个示例中所做的是生成一个Timer(在Future.delayed 后面完成),它稍后会执行并调用completer.complete。这为我们的Future 提供了一个值,它在偶数队列中添加一个事件,以便我们的未来订阅者作为我们的下一个事件执行。
    猜你喜欢
    • 2020-04-18
    • 2021-03-18
    • 2019-06-11
    • 1970-01-01
    • 2019-10-24
    • 2023-02-25
    • 2020-08-31
    • 1970-01-01
    • 2019-06-26
    相关资源
    最近更新 更多