如何使异步Dart调用同步?

时间:2015-01-30 14:35:53

标签: asynchronous dart synchronous

通过将各种Java程序移植到Dart并比较和分析结果,我正在为Dart评估德国公司。在浏览器中,Dart赢得了胜利。对于服务器软件而言,性能似乎是一个严重的问题(参见this question of me),但这种情况大部分已被解除。

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

据我所知,我可以将可用的同步软件重组为异步软件。但这会将设计良好的软件转换为可读性较低且难以调试和维护的软件。对于某些软件而言,这是没有意义的。 我的问题:是否存在(被我忽视)将异步调用嵌入到同步调用方法中的方法?

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

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

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

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

  return result;
}

使用这样的方法也可以简化lib API。大多数"同步"可以删除呼叫,因为重新同步呼叫可以完成这项工作。这似乎是一个合乎逻辑的想法,我仍然认为它存在,我错过了它。或者有一个严重的原因,为什么这不起作用?

<小时/> 在考虑了lm(见下文)收到的答案两天后,我仍然不明白为什么不应该将异步Dart调用封装到同步调用中。它是在&#34; normal&#34;同步编程世界。通常你可以通过获得&#34;完成&#34;等待重新同步。来自异步例程或者如果在超时后某些事情继续失败。

考虑到这一点,我的第一个提案可以像这样增强:

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

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

我相信我也在lm的论证中找到了主要论点的解决方案。因此,我的问题仍然是关于这个&#34;增强&#34;我提出的解决方案:在Dart中是否有任何真正无法实现的解决方案?

9 个答案:

答案 0 :(得分:6)

只有当您不需要获取返回值时,才能将异步方法包装在同步方法中。

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

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异步,则不仅需要使用异步模式调用它,而且整个方法体将异步运行,因此在函数返回时不会禁用保存按钮。 / p>

为完整起见,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;
  }
}

答案 1 :(得分:2)

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

异步执行具有传染性。同步函数必须在任何其他异步事件执行之前返回,因此无法同步等待异步执行。

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

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

此外,等待“所有异步执行”在基于事件的系统中没有明确定义。可能有广播流发出来自网络的事件,周期性定时器或接收端口从另一个隔离区获取数据,或者您不能等待的一些其他事件源,因为它们来自隔离区外部,或者事件过程。当当前隔离关闭时,它可能会向另一个隔离发送最终关闭消息,因此有效的“异步执行”在隔离器死亡之前不会结束。

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

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

它不会等待Future返回的asyncFunction中未反映的异步操作,但asyncFunction的工作在其操作完成之前无法完成。

答案 2 :(得分:1)

Dart天生就是异步。试图避免异步不会失败。 某些API调用的同步版本例如在dart:io中,在某些情况下,使用它们似乎更简单,但因为没有所有方法/功能的同步版本,您无法避免完全异步。

随着最近推出的async / await功能编程异步变得更加简单,代码看起来几乎就像同步代码(但它不是。)

如果呼叫异步,则保持异步。据我所知,你无能为力。

答案 3 :(得分:1)

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

有一种 方法,但是Dart文档对此提出警告(尽管并没有真正讨论其含义,但还是有某种“实验性”)。

waitFor命令。

您基本上传递了一个异步函数,该函数返回一个Future,一个可选的timeout参数,而waitFor函数将返回结果。

例如:

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

答案 4 :(得分:0)

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();
}

答案 5 :(得分:0)

/ *因为Await语句只能在异步方法中使用。然后我们执行两种方法。我认为首先调用异步方法,然后不断查询非异步方法的空结果。然后我们得到一个同步模型。这样,我们将在非异步方法中等待答案。我想到了这种方法。但是,据我所知,用飞镖语言无法摆脱异步工作模型。需要习惯它。这可能不专业,但是我想分享一下我想到的解决方案。希望对您有帮助。

 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;
  }

答案 6 :(得分:0)

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

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

例如,通过使用 async/await 并让 Dart 完成繁重的工作,您可以在不导致 UI 挂起的情况下运行网络代码:

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);
    });
  }
}

答案 7 :(得分:-1)

这是一个解决方案,其中,当调用几乎同时进入时,将异步函数的启动与启动时间间隔至少1秒钟交错。

步骤:

  1. 使用lastKnownTime来计算增量,其中初始值为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
    }

答案 8 :(得分:-1)

我来这里是为了找到答案,但想出了一种方法,如下所示,并且可以正常工作。

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;

不客气。