如何使异步 Dart 调用同步?

How to make an asynchronous Dart call synchronous?

我正在为一家德国公司评估 Dart,通过将各种 Java 程序移植到 Dart 并比较和分析结果。在浏览器中,Dart 轻而易举地获胜。服务器软件性能似乎是一个严重的问题(请参阅 ),但大多数情况下都已解决。

现在我正在移植一些 "simple" 命令行工具,我根本没想到会出现任何严重问题,但至少有一个。一些工具确实会发出 HTTP 请求来收集一些数据,而独立的 Dart 虚拟机仅以异步方式支持它们。浏览所有我能发现的,似乎不可能在一个大部分同步的软件中使用任何异步调用。

我知道我可以将可用的同步软件重组为异步软件。但这会将设计良好的软件变成可读性较差且更难调试和维护的软件。对于某些软件来说,这是没有意义的。 我的问题:是否有一种方法(被我忽略了)将异步调用嵌入到同步调用的方法中?

我想提供一个只能在主线程内使用的系统调用并不难,它只是将执行转移到整个排队的异步函数调用列表(不必结束主线程第一个)并且一旦最后一个被执行 returns 并继续主线程。

可能看起来像这样的东西:

var synchFunction() {
  var result;
  asyncFunction().then(() { result = ...; });

  resync(); // the system call to move to and wait out all async execution

  return result;
}

拥有这样的方法也会简化 lib API。大多数 "sync" 调用都可以删除,因为重新同步调用可以完成这项工作。这似乎是一个合乎逻辑的想法,我仍然认为它以某种方式存在并且我错过了它。还是有一个严重的原因导致它不起作用?


在思考 lm 收到的答案(见下文)两天后,我仍然不明白为什么不能将异步 Dart 调用封装到同步调用中。它一直在 "normal" 同步编程世界中完成。通常,您可以通过从异步例程中获取 "Done" 或在超时后继续执行某些操作来等待重新同步。

考虑到这一点,我的第一个提案可以这样改进:

var synchFunction() {
  var result;
  asyncFunction()
    .then(() { result = ...; })
    .whenComplete(() { continueResync() }); // the "Done" message

  resync(timeout); // waiting with a timeout as maximum limit

  // Either we arrive here with the [result] filled in or a with a [TimeoutException].
  return result;
}

resync() 与通常在结束 isolate 的 main 方法后发生的相同,它开始执行排队的异步函数(或等待事件使它们可执行)。一旦遇到 continueResync() 调用,就会设置一个标志,停止异步执行,并 resync() returns 到主线程。如果在给定的 timeout 期间没有遇到 continueResync() 调用,它也会中止异步执行并留下 resync() TimeoutException

对于某些受益于直接同步编程的软件组(不是客户端软件也不是服务器软件),这样的功能将为必须处理仅异步库的程序员解决很多问题。

我相信我也找到了下面lm论证中主要论点的解决方案。因此,关于我提出的这个 "enhanced" 解决方案,我的问题仍然存在:有什么东西真的无法在 Dart 中实现吗?

Dart 本质上是异步的。试图避免异步是行不通的。 例如在 dart:io 中有一些 API 调用的同步版本,在某些情况下使用它们似乎更简单但是因为没有所有 methods/functions 的同步版本你可以'不要完全避免异步。

最近引入了 async/await 功能,异步编程变得更加简单,代码看起来几乎像同步代码(但它不是)。

如果调用异步,它将保持异步。据我所知,你对此无能为力。

resync 函数无法在 Dart 当前的执行模型中实现。

异步执行具有传染性。同步函数必须 return 才能执行任何其他异步事件,因此无法同步等待异步执行。

Dart 中的执行是单线程和基于事件的。如果 resync 函数不阻塞同一隔离中的所有其他执行,则无法阻塞,因此挂起的异步操作永远不会发生。

要阻止同步执行并继续执行其他操作,您需要保留到那时为止的整个调用堆栈,并在同步操作完成后恢复它。如果你有那个功能,那么可能有比 Future 和 Stream 更好的方法来做事:)

此外,等待 "all async execution" 在基于事件的系统中没有明确定义。可能有一个广播流从网络发出事件,一个周期性定时器,或者一个从另一个隔离区获取数据的接收端口,或者你不能等待的一些其他事件源,因为它们来自隔离区之外,或者事件过程。当前 isolate 关闭时,它可能会向另一个 isolate 发送最终关闭消息,因此实际上 "async execution" 直到 isolate 死亡才结束。

使用 async/await 语法,您不会获得同步操作,但编写类似的异步操作会更容易:

function() async {
  var result = await asyncFunction();
  return result;
}

它不会等待 Future return 中未反映的异步操作 asyncFunction,但这是 asyncFunction 的工作完成直到其操作完成。

只有在不需要获取 return 值时才可以将异步方法包装在同步方法中。

例如,如果您想禁用保存按钮,将结果异步保存到服务器并在作业完成后重新启用保存按钮,您可以这样写:

Future<bool> save() async {
  // save changes async here
  return true;
}

void saveClicked() {
  saveButton.enabled = false;
  save()
    .then((success) => window.alert(success ? 'Saved' : 'Failed'))
    .catchError((e) => window.alert(e))
    .whenComplete(() { saveButton.enabled = true; });
}

注意saveClicked方法是完全同步的,但是异步执行save方法。

请注意,如果您使 saveClicked 异步,您不仅必须使用异步模式调用它,而且整个方法体都将 运行 异步,因此保存按钮不会被禁用当函数 returns.

为了完整起见,saveClicked 的异步版本如下所示:

Future<Null> saveClicked() async {
  saveButton.enabled = false;
  try {
    bool success = await save();
    window.alert(success ? 'Saved' : 'Failed');
  }
  catch (e) {
    window.alert(e);
  }
  finally {
    saveButton.enabled = true;
  }
}

是的,这已经晚了,但我认为这是新手应该知道的一个很酷的功能。

一种方法,但 Dart 文档警告反对它(并且它在某种程度上 "experimental",尽管没有真正讨论其含义)。

waitFor 命令。

你基本上传入了一个异步函数,return 是一个 Future,一个可选的 timeout 参数,waitFor 函数将 return 结果.

例如:

final int number = waitFor<int>(someAsyncThatReturnsInt);

这是一个基于错开异步函数启动的解决方案,当调用几乎同时进入时,启动时间至少相隔 1 秒。

步骤:

  1. 使用lastKnownTime计算delta,初始值为0

  2. 一旦增量不是某个巨大的数字,您就知道这是一个重复调用。


    class StartConversationState extends State<StartConversationStatefulWidget> {

      @override
      Widget build(BuildContext context) {

        _delayPush(); // this is the call that gets triggered multiple times
      }

      int lastKnownTime = 0;
      int delayMillis = 3000;

      _delayPush() async {
        delayMillis += 1500;

        await new Future.delayed(Duration(milliseconds: delayMillis));

        int millisSinceEpoch = new DateTime.now().millisecondsSinceEpoch;
        int delta = millisSinceEpoch - lastKnownTime;

        // if delta is less than 10 seconds, means it was a subsequent interval
        if (delta < 10000) {

          print('_delayPush() , SKIPPING DUPLICATE CALL');
          return;
        }

        // here is the logic you don't want to duplicate
        // eg, insert DB record and navigate to next screen
    }

import 'package:synchronized_lite/synchronized_lite.dart';

import 'dart:async';

// Using Lock as a mixin to further mimic Java-style synchronized blocks
class SomeActivity with Lock {

  bool _started = false;

  Future<bool> start() async {
    // It's correct to return a Future returned by synchronized()
    return synchronized(() async {
      if(_started)
        return false;
      // perform the start operation
      await Future.delayed(Duration(seconds: 1));
      print("Started");
      _started = true;
      return true;
    });
  }

  Future<void> stop() async {
    // It's also correct to await a synchronized() call before returning
    // It's incorrect to neither await a synchronized() call nor return its Future.
    await synchronized(() async {
      if(!_started)
        return;
      // perform the stop operation`enter code here`
      await Future.delayed(Duration(seconds: 1));
      print("Stopped");
      _started = false;
    });
  }
}

// Prints:
//   Started
//   Stopped
main() async {
  var a = SomeActivity();
  print("Hello");
  a.start();
  a.start();
  a.stop();
  await a.stop();
}

我来这里是为了寻找答案,但想出了一个方法,如下所示并且有效。

Future<dynamic> lastCallFuture;

Future<T> myAsyncFunction<T>(T value) async {
  if(lastCallFuture != null) {
    await lastCallFuture;
  }
  return lastCallFuture = _myAsyncFunction(value);
}

Future<T> _myAsyncFunction<T>(T value) async => value;

不客气。

/*由于Await语句只能在异步方法中使用。然后我们做两个 methods.I 思考,首先我们调用异步方法,然后我们不断地查询非异步方法的空结果。然后我们得到一个同步模型。这样,我们将在非异步方法中等待答案。我想到了这样一种方法。但据我所知,flutter dart 语言的异步工作模型是逃不掉的。需要习惯 it.It 可能不专业,但我想分享一下我想到的解决方案。希望对你有帮助。

 Stock resultStockQueryByBarcodeAsync;
  bool waitStockQueryByBarcodeAsyncCompleted = false;

  Stock WaitStockQueryByBarcodeAsync(String barcode, int timeOut) {
    CallStockQueryByBarcodeAsync(barcode);
    var startTime = new DateTime.now();
    while (!waitStockQueryByBarcodeAsyncCompleted) {
      Duration difference = DateTime.now().difference(startTime);
      if (difference.inMilliseconds > timeOut) {
        throw TimeoutException("Timeout Exceeded");
      }
      //we must scope time. Because it can be enter endless loop.
    }
    return resultStockQueryByBarcodeAsync;
  }

  void CallStockQueryByBarcodeAsync(String barcode) async {
    waitStockQueryByBarcodeAsyncCompleted = false;
    resultStockQueryByBarcodeAsync = null;
    var stock = await StockQueryByBarcodeAsync(barcode);/*your target async method*/
    waitStockQueryByBarcodeAsyncCompleted = true;
    resultStockQueryByBarcodeAsync = stock;
  }

Dart 具有单线程执行模型,支持 Isolates(一种在另一个线程上执行 运行 Dart 代码的方法)、事件循环和异步编程。除非你生成一个 Isolate,否则你的 Dart 代码 运行s 在主 UI 线程中并且由事件循环驱动。 Flutter的事件循环相当于iOS主循环——也就是依附于主线程的Looper

Dart 的单线程模型并不意味着您需要 运行 将所有内容作为导致 UI 冻结的阻塞操作。相反,使用 Dart 语言提供的异步工具,例如 async/await 来执行异步工作。

例如,您可以 运行 网络代码,而不会导致 UI 挂起,方法是使用 async/await 并让 Dart 完成繁重的工作:

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = jsonDecode(response.body);
  });
}

awaited 网络调用完成后,通过调用 setState() 更新 UI,这将触发小部件子树的重建并更新数据。

以下示例异步加载数据并将其显示在 ListView:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView.builder(
          itemCount: widgets.length,
          itemBuilder: (BuildContext context, int position) {
            return getRow(position);
          }));
  }

  Widget getRow(int i) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Text("Row ${widgets[i]["title"]}")
    );
  }

  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = jsonDecode(response.body);
    });
  }
}