如何使异步 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 秒。
步骤:
使用lastKnownTime
计算delta,初始值为0
一旦增量不是某个巨大的数字,您就知道这是一个重复调用。
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);
});
}
}
我正在为一家德国公司评估 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 秒。
步骤:
使用
lastKnownTime
计算delta,初始值为0
一旦增量不是某个巨大的数字,您就知道这是一个重复调用。
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);
});
}
}