我喜欢在我的Dart应用程序中模拟一个异步web服务调用进行测试。为了模拟这些模拟调用响应的随机性(可能是无序的),我想编程我的模拟在返回“Future”之前等待(睡眠)一段时间。

我该怎么做呢?


当前回答

我发现在Dart中有几个实现可以使代码延迟执行:

new Future.delayed(const Duration(seconds: 1)); //recommend

new Timer(const Duration(seconds: 1), ()=>print("1 second later."));

sleep(const Duration(seconds: 1)); //import 'dart:io';

new Stream.periodic(const Duration(seconds: 1), (_) => print("1 second later.")).first.then((_)=>print("Also 1 second later."));
//new Stream.periodic(const Duration(seconds: 1)).first.then((_)=>print("Also 1 second later."));

其他回答

Dart在单个进程或线程(事件循环)中运行您的所有代码。因此,异步块/方法中的代码,实际上是在某种“全局”序列中运行的,这种“全局”序列提供了事件循环。 换句话说:在Dart中没有,只有一个“线程”(目前不包括“隔离”,见下文)。

当必须模拟数据时,随着时间的推移到达,例如通过流,人们可能会陷入在异步块/方法中运行的代码中使用睡眠的陷阱。思考:“我的代码启动了一个新的线程,并愉快地继续前进,并在UI层保持响应!” 但事实并非如此:在《Dart》中,你只是射中了自己的膝盖。

当在任何地方使用sleep时,不仅仅是在异步代码中,这将停止“唯一的”事件循环,每个“线程”都被阻塞,也就是说,所有的代码,所有的东西……(…因为实际上不像在Java中那样有“线程”)。

注意:最重要的是,UI会阻塞,不会重绘,也不会响应任何输入。所有在睡眠期间发生的幕后事件,只有在“醒来”后才会变得可见。

例如,假设一个列表将项目附加到自身(可能通过流/ StreamBuilder)。在“真实的应用生活”中,数据通常来自“外部来源”,以延迟的、不可预测的方式发送数据。所以,一切都好……

...但是当数据在你的代码中被模仿时,比如通过一个按钮事件,涉及睡眠,sync或async,没有关系,列表只会在最后重新绘制,当所有的睡眠结束时。此外,任何其他按钮事件,例如,也不会分派事件循环,直到之后。

考虑下面的代码:

Future<VoidCallback?> asyncWithSleep() async {
  print('start');
  for (var i = 0; i < 5; i++) {
    sleep(const Duration(seconds: 1));
    print(i);
  }
  print('end');
  return null;
}

打印:

flutter: start
flutter: 0
flutter: 1
flutter: 2
flutter: end

如果您希望end在数字之前打印,那么现在大声说出来:“sleep in Dart导致所有线程等待”。基本上,除非您从上到下运行命令行脚本,或者在隔离中运行代码,否则请忘记睡眠。

例如,要模拟来自某些源的数据,如web服务、数据库或底层平台,你可以为你想要模拟的每一块数据生成Future.delayed。

DO

void syncWithDelayedFutureAndNoSyncDownTheLine() { // doesn't have to be async but could, if Future setup takes too long
  print('start');
  for (var i = 0; i < 3; i++) {
    Future.delayed(const Duration(seconds: i + 1), () {
      print(i);
    });
  }
  print('end');
  return null;
}

这既不会阻塞UI也不会阻塞其他任何东西。它设置了三块将来异步运行的代码,分别在for循环的1秒、2秒和3秒内。

打印:

flutter: start
flutter: end
flutter: 0
flutter: 1
flutter: 2

(如果每秒钟模拟一次数据似乎太无聊,可以在Future.delayedparameter的Duration参数中添加一些波动和随机性…)

为模拟或测试创建未来事件的关键是正确设置未来的持续时间。在异步代码中阻塞并不像预期的那样工作,因为它阻塞了所有。

如果您仍然认为需要在程序的任何地方使用睡眠,请查看Dart的隔离。我还没有使用过它们,但据我所知,它们看起来有点像Java线程,没有共享内存的负担,而且有很多陷阱。然而,它们是作为“后台工作人员”,用于计算/时间密集的处理,当从相同的main()运行时,这可能会使程序的其余部分变得缓慢,也就是说,在相同的main隔离中。

你也可以使用future .delayed工厂来完成延迟后的future。下面是两个函数在延迟后异步返回字符串的示例:

import 'dart:async';

Future sleep1() {
  return new Future.delayed(const Duration(seconds: 1), () => "1");
}

Future sleep2() {
  return new Future.delayed(const Duration(seconds: 2), () => "2");
}

它并不总是你想要的(有时你想要Future.delayed),但如果你真的想在Dart命令行应用程序中睡觉,你可以使用Dart:io的sleep():

import 'dart:io';

main() {
  sleep(const Duration(seconds:1));
}

如果你需要在一段时间后执行一些代码

        Future.delayed(const Duration(seconds: 5), () {
//do something
        });

这是一个有用的模拟,可以接受一个可选参数来模拟错误:

  Future _mockService([dynamic error]) {
    return new Future.delayed(const Duration(seconds: 2), () {
      if (error != null) {
        throw error;
      }
    });
  }

你可以这样使用它:

  await _mockService(new Exception('network error'));