0

语境

下面的代码代表一种抽象,其中MyClass是某种下载管理器。

import 'dart:async';

Future<void> main() async {
  MyClass().test().listen((v) => print('t1: $v')).onError(print);
  final commomClass = MyClass();
  commomClass.test().listen((v) => print('t2: $v')).onError(print);
  commomClass.test().listen((v) => print('t3: $v')).onError(print);
}

class MyClass {
  bool _isDownloadInProgress = false;
  int _i = 0;
  StreamController<int> _sc;
  
  Stream<int> test() async* {
    if (_isDownloadInProgress) {
      throw Exception('Download already in progress');
    } else {
      _sc = StreamController<int>();
    }
    
    Timer.periodic(
      const Duration(seconds: 1),
      (t) {
        if (_i == 4) {
          _isDownloadInProgress = false;
          _i = 0;
          _sc.close();
          t.cancel();
        } else {
          _sc.add(_i++);
        }
      },
    );

    yield* _sc.stream;
  }
}

问题

我希望在执行此代码后,它会生成值 t1 和 t2,并且输出 t3 只会生​​成一次“正在下载”。例如:

t1: 0
t2: 0
t3: Download already in progress
t1: 1
t2: 1
t1: 2
t2: 2
t1: 3
t2: 3

但它会输出所有四个 t1值、八个 t3值,并且没有“正在下载”消息:

t1: 0
t3: 0
t3: 1
t1: 1
t3: 2
t3: 3
t1: 2
t3: 0
t1: 3
t3: 1
t3: 2
t3: 3

对我来说,这些t1值会正确输出,t2也会正确输出,并且t3会输出“正在下载”消息,因为一切都是异步运行的,它会尝试“下载”已经下载的内容(因为该test()方法是在MyClass) 的同一个实例上调用的。

我错过了什么?

4

2 回答 2

1

您忘记_isDownloadInProgress = true;在函数内部进行设置。

尝试:

if (_isDownloadInProgress) {
    throw Exception('Download already in progress');
} else {
  _sc = StreamController<int>();
  _isDownloadInProgress = true;
}

输出

Exception: Download already in progress
t1: 0
t2: 0
t1: 1
t2: 1
t1: 2
t2: 2
t1: 3
t2: 3

您可以t3通过延迟它的开始来使其更有趣:

Future.delayed(const Duration(seconds: 2),
      () => commomClass.test().listen((v) => print('t3: $v')).onError(print));

输出

t1: 0
t2: 0
Exception: Download already in progress
t1: 1
t2: 1
t1: 2
t2: 2
t1: 3
t2: 3
于 2020-08-25T22:02:56.197 回答
1

对于初学者,您的代码永远不会设置_isDownloadInProgress为 true,因此没有理由出现“正在下载”。

这实际上是导致第二个错误的原因。当您调用 t3listen时,因为_isDownloadInProgress始终为假,_sc除了新Timer.periodic的入队之外,这还会导致被覆盖。当每个计时器触发时,它引用t3 ,_sc现在是包含 t3 的那个listen,因此您最终会得到两个计时器将事件推送到同一个流控制器,这就是为什么您会看到双倍的 t3 事件。

在实例化计时器之前简单地设置_isDownloadInProgress = true就足以获得预期的结果:

class MyClass {
  bool _isDownloadInProgress = false;
  int _i = 0;
  StreamController<int> _sc;
  
  Stream<int> test() async* {
    if (_isDownloadInProgress) {
      throw Exception('Download already in progress');
    } else {
      _sc = StreamController<int>();
    }
    
    _isDownloadInProgress = true; // Add this line
    Timer.periodic(
      const Duration(seconds: 1),
      (t) {
        if (_i == 4) {
          _isDownloadInProgress = false;
          _i = 0;
          _sc.close();
          t.cancel();
        } else {
          _sc.add(_i++);
        }
      },
    );

    yield* _sc.stream;
  }
}

结果:

Exception: Download already in progress
t1: 0
t2: 0
t1: 1
t2: 1
t1: 2
t2: 2
t1: 3
t2: 3
于 2020-08-25T22:03:26.770 回答