【问题标题】:How do I ensure a specific execution order when forced to use Dart async/await强制使用 Dart async/await 时如何确保特定的执行顺序
【发布时间】:2021-12-18 16:23:36
【问题描述】:

我正在编写一个基本程序来自学 Dart/Flutter。部分程序使用http.dart 包获取一些数据,http.get 命令返回Future 值。为了解压这个值,我需要使用await 命令,然后更改我的代码的执行顺序。在使用async/await 时,我无法弄清楚如何保留预期的执行顺序。我是新手,所以很感激我可能遗漏了一些明显的东西。

下面的代码示例1通过一系列函数使用async/await。这种方法或多或少给出了正确的输出顺序(main() 的结尾除外),但意味着(我认为)我需要一个 async build() 方法,这在 Flutter 中无效。

// Cascading async methods with local variables and await statements
import 'dart:async';
import 'dart:math';
import 'package:http/http.dart' as http;

void main(List<String> arguments) {
  print('Main: start.');
  _build();
  print('Main: end.');
}

// Draw some stuff, make some decisions
void _build() async {
  print('build: Before getName.');
  String name = await _getName();
  print('build: After getName.');
}

// Get some data, make some decisions
Future<String> _getName() async {
  print('getName: before getData');
  String name = await _getData();
  print('getName: after getData');
  double val = Random().nextDouble();
  if (val < 0.5) {
    print('getName: returning body.data');
    return name;
  } else {
    print('getName: returning Bob');
    return 'Bob';
  }
}

// Get the data via an http request
Future<String> _getData() async {
  print('getData: Before http get.');
  final data = await http.get(Uri.parse('http://www.google.co.uk'));
  print('getData: After http get.');
  return data.body;
}

由此产生的输出是(我已经截断了返回的 html 数据):

Main: start.
build: Before getName.   
getName: before getData  
getData: Before http get.
Main: end.
getData: After http get.
getName: after getData
getName: returning body.data
build: After getName.  Name is: <html data>

下面的第二个代码示例使用全局变量在_getName() 方法中捕获数据,这样我就可以避免在build() 方法中使用async/await。这并没有给出正确的执行顺序或正确的输出。

// Use global variable to receive awaited data and avoid cascading async methods
import 'dart:async';
import 'dart:math';
import 'package:http/http.dart' as http;

String name = "";

void main(List<String> arguments) {
  print('Main: start.');
  _build();
  print('Main: end.');
}

// Draw some stuff, make some decisions
void _build() {
  print('build: Before getName.');
  _getName();
  print('build: After getName.  Name is: $name');
}

// Get some data, make some decisions
Future<void> _getName() async {
  print('getName: before getData');
  String data = await _getData();
  print('getName: after getData');
  double val = Random().nextDouble();
  if (val < 0.5) {
    print('getName: setting name = body.data');
    name = data;
  } else {
    print('getName: setting name = Bob');
    name = 'Bob';
  }
  return;
}

// Get the data via an http request
Future<String> _getData() async {
  print('getData: Before http get.');
  final data = await http.get(Uri.parse('http://www.google.co.uk'));
  print('getData: After http get.');
  return data.body;
}

此代码的输出如下所示。请注意,build() 方法在_getData_getName 之前完成,build() 中打印的名称在第 5 行为空。

Main: start.
build: Before getName.   
getName: before getData  
getData: Before http get.
build: After getName.  Name is: 
Main: end.
getData: After http get.
getName: after getData
getName: setting name = body.data

在下面的第三个示例中,我尝试使用.then 来确保每个函数中的代码仅在等待命令之后执行。我不认为这会起作用(而且它没有),因为我认为我在控制函数之间的流程时遇到了问题,而不是在控制函数内的流程时遇到了问题,但我认为我应该试一试,我正在抓紧到此为止。

// Use global variable to avoid using await in build() method
// Use .then to ensure that method actions follow await command

import 'dart:async';
import 'dart:math';
import 'package:http/http.dart' as http;

String name = ""; // Global variable for async data return

void main(List<String> arguments) {
  print('Main: start.');
  _build();
  print('Main: end.');
}

// Draw some stuff, make some decisions
void _build() {
  print('build: Before getName.');
  _getName();
  print('build: After getName.  Name is: $name');
}

// Get some data, make some decisions
Future<void> _getName() async {
  print('getName: before getData');
  await _getData().then((data) {
    print('getName: after getData');
    double val = Random().nextDouble();
    if (val < 0.5) {
      print('getName: setting name = body.data');
      name = data;
    } else {
      print('getName: setting name = Bob');
      name = 'Bob';
    }
  });
  return;
}

// Get the data via an http request
Future<String> _getData() async {
  print('getData: Before http get.');
  String value = "";
  await http.get(Uri.parse('http://www.google.co.uk')).then((data) {
    print('getData: After http get.');
    value = data.body;
  });
  return value;
}

此代码的输出如下所示。与第二个示例一样,执行顺序不正确,build() 方法中打印的名称为空。

Main: start.
build: Before getName.   
getName: before getData  
getData: Before http get.
build: After getName.  Name is: 
Main: end.
getData: After http get.
getName: after getData     
getName: setting name = Bob

理想情况下,程序的输出应该是:

Main: start.
build: Before getName.   
getName: before getData  
getData: Before http get.
getData: After http get.
getName: after getData     
getName: setting name = Bob
build: After getName.  Name is: Bob 
Main: end.

如何编写我的代码,以便我可以使用http.get 方法并确保我的代码按我想要的顺序执行?我只想补充一点,我已经阅读了很多 stackoverflow 问题、flutter 文档和在线一般帮助,但到目前为止还没有找到任何可以回答我问题的内容。 或者我没有理解。 :D 抱歉,如果这是一个愚蠢的问题。在这方面我是菜鸟。

我应该补充一点,这个例子是对我正在编写的 Flutter 应用程序中的问题的简化(noughts and crosss)。这是在每次移动后检查获胜/平局,然后从数据库读取数据,更新结果并将它们写回数据库。它还会更新游戏状态以显示游戏结束。由 async/await 引起的问题是,当函数等待数据时,游戏状态没有更新,即使游戏结束,游戏也会继续处于“正在玩”状态。下面程序的伪代码(这有点杂乱无章,但希望它能说明问题)。

build() {
  checkState(win, draw or continue?);
    if (continue){
      _humanMove();
      _computerMove();
      drawTheWidgets;
    } else {
      drawDifferentWidgets; // New game
    }
}

void _humanMove() async {
    processMove;
    if (win/draw) { 
      await _updateResults; 
      updateGameState(game over);
    }
}

void _computerMove() async {
    processMove;
    if (win/draw) { 
      await _updateResults; 
      updateGameState(game over);
    }
}

results _updateResults() async {
  await http.get data results in database;
  updateWithResult;
  await http.put data results in database;
}

【问题讨论】:

  • 这能回答你的问题吗? What is a Future and how do I use it?
  • 您明确声明 _build 的返回类型为 void 而不是 Future&lt;void&gt;。这可以防止调用者在完成时收到通知(_build 因此是一个“即发即弃”功能)。如果你想让main等待_build完成,它必须返回一个Future,并且main必须await它。
  • 我知道它已经过时了,但这必须是有史以来最好的视频之一,才能理解斯蒂芬的以下观点:你需要在得到结果之前渲染一些东西(例如占位符),然后再渲染一些不同的东西你有他们。 youtube.com/…
  • 感谢@ChristopherMoore - 我已经阅读了有关 Futures 的信息,但这是一种非常简单的解释方式并且很有用。这让我想知道我是否应该使用全局变量并对其进行测试以查看它是否具有值(即承诺已履行),然后在数据可用(或读取失败)后让代码继续执行。虽然这感觉有点 hacky - 我总是被教导全局变量是不好的。这本质上是程序显示进度条或微调器时发生的情况吗?我认为这就是我需要做的(但没有微调器)。
  • 谢谢@jamesdlin。我在原始帖子中添加了一些信息以澄清我的问题,并在下面回复了 Stephan 的帖子。我最初确实走这条路,但在我的 Flutter 应用程序中,这意味着我必须创建build() async,这意味着它必须返回一个Future,这意味着它不再是build() 方法的有效覆盖.

标签: flutter dart asynchronous async-await


【解决方案1】:

所有函数调用都需要等待(“await”),参见:

void main(List<String> arguments) async {
  print('Main: start.');
  await _build();
  print('Main: end.');
}

// Draw some stuff, make some decisions
Future<void> _build() async {
  print('build: Before getName.');
  await _getName();
  print('build: After getName.  Name is: $name');
}

// Get some data, make some decisions
Future<void> _getName() async {
  print('getName: before getData');
  String data = await Future.delayed(const Duration(milliseconds: 500), () {
    return 'x';
  });
  print('getName: after getData');
  double val = 5;
  if (val < 0.5) {
    print('getName: setting name = body.data');
    name = data;
  } else {
    print('getName: setting name = Bob');
    name = 'Bob';
  }
  return;
}

一条评论:我相信如果您尝试不基于控制台输出,而是使用 Flutter 应用程序来学习这一点,那么您会更容易学会正确地做到这一点。您会看到您的程序经历了实际需要时间的不同状态——在您的 http 请求之前、期间和之后。 在小部件的构建方法中,您需要为程序的每个状态提供一些显示内容。所以你实际上并不等待(等待)结果,而是根据结果更新状态。取决于此,您的构建方法已准备好采用不同的状态并显示适当的内容。那么 async/await 就很不错了。

---添加以下内容以回应澄清评论---

小部件的构建方法不是您放置应用业务逻辑的地方。每当重建小部件时都会调用 Build(如果您有一个在数据库中写入的 http 请求,它将被触发很多次!)。尝试在控制台上的预期输出中应用您希望应用程序在您的第一个描述中执行的步骤,我尝试编写此澄清应用程序(并让您的应用程序通过不同的状态,我包括您必须单击图标button / FloatingActionButton 来触发流程)。请看一下第二个小部件的构建方法如何处理所有不同的状态(它将它们打印到控制台并显示文本 - 当然,根据您的应用程序的状态,您可以在那里做更多花哨的东西) .真正的“动作”(应用状态的变化)发生在别处:

import 'package:flutter/material.dart';

class ExampleWidget extends StatefulWidget {
  const ExampleWidget({Key? key}) : super(key: key);

  @override
  _ExampleWidgetState createState() => _ExampleWidgetState();
}

class _ExampleWidgetState extends State<ExampleWidget> {
  String _showWhatsHappening = 'Before getName.';

  Future<String> _getData() async {
    setState(() {
      _showWhatsHappening = 'getData: Before http get.';
    });
    final data = await Future.delayed(const Duration(milliseconds: 500), () {
      return 'Bob';
    });
    setState(() {
      _showWhatsHappening = 'getData: After http get.';
    });
    await Future.delayed(const Duration(milliseconds: 300));
    return data;
  }

  @override
  void initState() {
    print('not main, but initstate: start.');
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      theme: ThemeData(brightness: Brightness.light, fontFamily: 'Example'),
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          child: const Icon(Icons.star_outline),
          onPressed: () async {
            setState(() {
              _showWhatsHappening = 'before getData  ';
            });
            final x = await _getData();
            await Future.delayed(const Duration(milliseconds: 300));
            setState(() {
              _showWhatsHappening = 'after getData  ';
            });
            await Future.delayed(const Duration(milliseconds: 300));

            setState(() {
              _showWhatsHappening = 'setting name = $x';
            });
          },
        ),
        body: Center(child: ShowItWidget(showIt: _showWhatsHappening)),
      ),
    );
  }
}

class ShowItWidget extends StatelessWidget {
  final String showIt;
  const ShowItWidget({Key? key, required this.showIt}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print(showIt);
    return Text(showIt);
  }
}

void main() {
  runApp(const ExampleWidget());
}

【讨论】:

  • 感谢您的快速回复。我应该在我的原始帖子中更清楚。代码示例基于我为学习 Flutter 而编写的 Flutter 应用程序(noughts 和 crosss)。一直使用 await 到顶部的问题是我必须使 build() 成为异步函数,这会引发错误 ('Future&lt;Widget&gt; Function(BuildContext)') isn't a valid override of 'State.build' ('Widget Function(BuildContext)'),因为 build() 正在被覆盖并且您无法将返回值更改为一个未来。我确实在段落中提到了这一点。 2,但应该更清楚。
  • 我更新了我的答案。请看一看!
  • 谢谢@Stephan。我会研究你的代码,看看我能不能弄清楚你在做什么。我还应该提到,我的代码使用 Stream 使部分 build() 代码等待 async 函数完成,但我知道这在真正的应用程序中会很糟糕。我也在看Isolates,它似乎提供了我想要的东西,但我还不能让它们工作。目前我的学习曲线非常陡峭!再次感谢您的帮助。
  • 好的,这一切都说得通(而且有效!)我可以看到您在onPressed:() async 之后编写了代码,而不是整个方法。我不知道你能做到这一点。 :) 所以...最后一个问题,我在哪里放置我的业务逻辑?在我的 noughts and crosses 应用程序中,一旦代码确定是否有人赢了,我就会读取数据,这发生在人类玩家单击单元格之后(即从 build() 开始使用 onPressed: 的交互)等等 DB​​ 代码(并测试 win 代码),同时在一个单独的函数中,链接 build() 调用。有没有更好的方法来做到这一点?
  • 这几乎是一个哲学问题:-D。您可以在此处找到状态管理方法的概述:docs.flutter.dev/development/data-and-backend/state-mgmt/… 首先,我个人建议您从 ChangeNotifierProvider 开始,请参阅flutterbyexample.com/lesson/change-notifier-provider
【解决方案2】:

为了完整起见,这是我使用 Streams 的特定于 Dart 的解决方案。

// Use global variable to receive awaited data
// Use StreamController to wait for async methods to finish
import 'dart:async';
import 'dart:math';
import 'package:http/http.dart' as http;

String name = "";
StreamController streamController = StreamController.broadcast();

void main(List<String> arguments) {
  print('Main: start.');
  _build();
  print('Main: end.');
}

// Draw some stuff, make some decisions
void _build() {
  print('build: Before getName.');
  _getName();

  streamController.stream.listen((args) {
    print('build: After getName.  Name is: $name');
  });
}

Future<void> _getName() async {
  print('getName: before getData');
  String data = await _getData();
  print('getName: after getData');

  double val = Random().nextDouble();
  if (val < 0.5) {
    print('getName: setting name = body.data');
    name = data.length.toString();
  } else {
    print('getName: setting name = Bob');
    name = 'Bob ${data.length.toString()}';
  }
  print('gateName.  Name is $name');
  streamController.add(name);
  return;
}

// Get the data via an http request
Future<String> _getData() async {
  print('getData: Before http get.');
  final data = await http.get(Uri.parse('http://www.google.co.uk'));
  print('getData: After http get.');
  return data.body;
}

【讨论】:

    猜你喜欢
    • 2022-08-14
    • 2019-07-01
    • 2018-12-15
    • 2021-09-30
    • 2020-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-16
    相关资源
    最近更新 更多