Isolate
基本概念
Isolate更像是一个进程,所有的dart代码都会在上面运行,其内部有一个线程,用来处理event loop,还有一块内存,并且这块内存是私有的,也就说两个Isolate不共享内存(这也是和Java 的Thread的主要区别),要是多个Isolate想共同工作,只能通过port来发送消息进行通信(一般会把特别耗时的操作,比如IO操作、图片压缩等容易引起main isolate卡顿的操作放到一个单独的Isolate中运行)。这样做的好处是分配内存和垃圾回收的时候不用对操作加锁,对于像flutter这种需要快速构建或者拆卸大量widget的应用来说很有帮助。
创建Isolate
两种方式创建:
- 通过静态方法
Isolate.spawn
创建(常用)
external static Future spawn(
void entryPoint(T message), T message,
{bool paused: false,
bool errorsAreFatal,
SendPort onExit,
SendPort onError,
@Since("2.3") String debugName});
entryPoint
入参必须是一个顶层函数或者一个static
方法,不接受一个函数表达式或者一个实例的方法,否则会Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function
的错误。
- 通过构造函数创建(几乎用不到)
Isolate(this.controlPort, {this.pauseCapability, this.terminateCapability});
使用这种方法创建Isolate
必须自己传入pause和terminate能力,否则调用pause
和kill
方法是没有任何效果的。
Isolate间通信
- 不同的Isolate之间通信是通过
ReceivePort
、SendPort
来实现的。 - 可以在Isoalte之间传输的数据类型有基础类型(
null
,num
,bool
,double
,String
)、SendPort
、List
、Map
(List
和Map
元素的类型也必须是允许传输的类型,允许传递SendPort
是实现双向通信的基础),实践中发现也能传递一些属性为基础类型的简单对象,在1.17.1版本中甚至能传递future对象。 -
ReceivePort
实现了Stream
类,是一个非广播的Stream,所以只允许有一个监听者。 - 通过看C的源码可以知道,每个Isolate其实对应一个线程,其背后是有一个线程池的。
- 创建Isolate时会创建对应的堆空间、消息处理handler等等对应的信息。
单向通信
void startOnWay() async {
ReceivePort receivePort = ReceivePort();
Isolate isolate = await Isolate.spawn(oneWayEntryPoint, receivePort.sendPort);
receivePort.listen((data) {
print('$data');
receivePort.close();//如果不关的,它会一直监听
isolate?.kill(priority: Isolate.immediate);//杀死isolate
isolate = null;
});
}
void oneWayEntryPoint(SendPort sendPort) {
sendPort.send('hello world!');
}
双向通信
void startBothWay() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(entryPointBothWay, receivePort.sendPort);
receivePort.listen((data) {
if (data is SendPort) {
data.send('hello child!');
} else {
print('parent: $data');
}
});
}
void entryPointBothWay(SendPort sendPort) {
ReceivePort r = ReceivePort();
sendPort.send(r.sendPort);
r.listen((data) {
print('child: $data');
sendPort.send('hello parent!');
});
}
compute函数
在flutter中实现isolate间通信,可以直接使用更方便的API--compute函数。它实际上是对isolate之间通信的封装,每次调用后都会执行isolate.kill方法。demo如下:
class Food {
String name;
double price;
Food(this.name, this.price);
}
class AsyncDemo extends StatefulWidget {
@override
_AsyncState createState() => _AsyncState();
}
class _AsyncState extends State {
var food = Food('apple', 23.1);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'''
name: ${food.name}
price: ${food.price}
''',
style: TextStyle(
fontSize: 22.0,
),
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
compute(handleCounter, food).then((value) {
setState(() {
food = value;
});
});
}),
);
}
}
Food handleCounter(Food food) {
return food..price = 1.8;
}
EventLoop
通过Isolate我们知道dart app是个单线程app,那他是怎么实现异步操作的呢?答案是EventLoop和他相关联的两个队列--event queue、microtask queue。
Event Queue
event queue包含几乎所有的事件,比如IO操作、绘制操作、多个isolate之间通信、Future的部分操作等等。
Microtask Queue
这个队列用来执行用时很短的事件,这些事件会在把控制权交给Event Queue之前运行完毕。在实际开发中使用场景十分有限,整个Flutter源码里面总共有7处使用到了Microtask Queue(flutter中将事件提交到Microtask Queue上是使用 scheduleMicrotask
api来操作的)。
执行顺序
从上图可以看到,总是会先执行microtask,当microtask queue空的时候才会去执行event queue里的事件,每执行完一个event,都会重新去执行microtask queue里的事件。如果执行microtask里的事件消耗太多的时间,容易造成event queue阻塞,从而造成app的卡顿。
看下面demo,
import 'dart:async';
main() {
print('main #1 of 2');
scheduleMicrotask(() => print('microtask #1 of 3'));
new Future.delayed(new Duration(seconds:1),// 1
() => print('future #1 (delayed)'));
new Future(() => print('future #2 of 4')) // 2
.then((_) => print('future #2a'))
.then((_) {
print('future #2b');
scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
})
.then((_) => print('future #2c')); // X
scheduleMicrotask(() => print('microtask #2 of 3'));
new Future(() => print('future #3 of 4')) // 3
.then((_) => new Future(
() => print('future #3a (a new future)')))// 5
.then((_) => print('future #3b'));// Y
new Future(() => print('future #4 of 4')); // 4
scheduleMicrotask(() => print('microtask #3 of 3'));
print('main #2 of 2');
}
输出:
main #1 of 2
main #2 of 2 // a
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3 // b
future #2 of 4
future #2a
future #2b // c
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #3a (a new future)
future #3b
future #1 (delayed)
分析:
程序会从上到下一次执行main方法里的同步方法,所以会依此输出到标 a的位置。scheduleMicrotask会把任务提交到microtask queue里,future会被提交到event queue里,所以先执行scheduleMicrotask的操作,对应输出中标记 b 的地方。microtaks queue里的事件都被处理完了,接下来,eventloop开始处理event queue里的事件。按道理来讲代码里标记 1 2 3 4的future会被依此放到event queue队列中,但是标 1 的地方有一秒的延迟,所以标 1 的地方会在一秒后才会被放到event queue里,所以此时event queue里的事件排序是:2 3 4。先执行 2,依此输出到标记 c 的地方。这个时候看到了一个scheduleMicrotask,所以会把这个事件放到microtask的队列中去,此时这个future还未执行完,继续执行,输出future #2c。执行完毕后,event queue里的事件变成了 3 4,回去接着执行microtask queue里的事件,输出microtask #0 (from future #2b)。此时microtask queue里没有事件了,接着执行event queue里的事件,也就是事件 3,输出future #3 of 4,当执行到 5 的时候,又生成了一个新的future,放到了event queue里,事件3也就执行完了,此时event queue里的事件变成了 4(等待执行) 5(等待执行)。接着执行事件4,输出future #4 of 4,接着执行事件 5,依此输出future #3a (a new future) future #3b,最后经过一秒后事件1入队被执行,也就是最后输出的future #1 (delayed)。
需要注意的是代码中标记X和Y的地方,标记X的地方,由于他的上一步并没有返回Future,所以标X的操作是在事件2后面进行的,而标记Y的地方,他上一步返回了一个Future对象,也就是事件 5,所以这个then函数里的操作是跟在事件5后面进行的。
Future
按官方文档上的介绍:它代表在将来某一时刻会获取到想要的值。当一个返回Future的函数被调用的时候,会发生两件事情:
- 这个Future对象会进去队列中排队等待被执行,此时会直接返回一个未完成状态的Future对象,但是这个函数是不进去队列的,它仅仅是被调用而已。
- 当值可获得的时候,Future处于一个叫 Completed with value 的状态,当执行失败时,处于一个叫做Completed with error的状态。
Future的三种状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQwBThcr-1589897339224)(/Users/jackshen/Pictures/1_UD63BMoIBmzoA6jo3LjCCg.png)]
要想使用Future,可以直接使用其提供的api,也可以使用dart提供的语法糖async、await。
async、await
main() {
fetchData();
}
void fetchData() async {
print('fetch data start');
await Future.delayed(Duration(seconds: 2));
print('Data is returned!');
}
//输出:
//fetch data start
//Data is returned!(两秒后会输出)
需要注意的是,在dart 2.0 之前,async中的同步代码不会运行,遇见async就直接返回一个未完成的Future对象。当被async标记的函数没有返回值时,会返回一个Future
Future API
Future()
void main() {
var future = Future((){
return 'hello world!';
});
future.then(print);
}
背后是通过Timer来实现的,直接看源码:
factory Future(FutureOr computation()) {
_Future result = new _Future();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
Future.delayed
Future.delayed(Duration(seconds: 1), () {
print('delayed 1');
});
1秒后输出相应值,其背后也是用的timer来实现,只不是传了一个duration对象过去。
factory Future.delayed(Duration duration, [FutureOr computation()]) {
_Future result = new _Future();
new Timer(duration, () {
if (computation == null) {
result._complete(null);
} else {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
}
});
return result;
}
Future.value
Future.value(1).then(print);
背后调用的源码如下(只考虑value非Future的情况下):
void _asyncCompleteWithValue(T value) {
_setPendingComplete();
_zone.scheduleMicrotask(() {
_completeWithValue(value);
});
}
可以看到背后并没有调用Timer相关api,使用的是scheduleMicrotask,也就是说Future.value函数是在mictotask queue里完成的,可以通过下面的例子验证:
Future.delayed(Duration(seconds: 0)).then((value) => print('Future.delayed'));
Future.value('Future.value').then(print);
//输出:
//Future.value ---优先执行microtask queue里的事件。
//Future.delayed
Future.sync
Future.sync(() => 'sync').then(print);
同步执行代码,下面是源码实现:
factory Future.sync(FutureOr computation()) {
try {
var result = computation();
if (result is Future) {
return result;
} else {
return new _Future.value(result as dynamic);
}
} catch (error, stackTrace) {
var future = new _Future();
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
future._asyncCompleteError(replacement.error, replacement.stackTrace);
} else {
future._asyncCompleteError(error, stackTrace);
}
return future;
}
}
要注意的是如果computation返回一个非future对象,会通过Future.value创建对象Future对象并返回,别忘了Future.value是在microtask 里完成的。再对比下下面两个例子的输出:
Demo1:
Future.delayed(Duration(seconds: 0)).then((value) => print('delayed'));
Future.sync(() => Future.delayed(Duration(seconds: 0)))
.then((value) => print('sync'));
//输出:
//delayed
//sync
Demo2:
Future.delayed(Duration(seconds: 0)).then((value) => print('delayed'));
Future.sync(() => 'sync').then(print);
//输出:
//sync
//delayed
Future.wait
final f1 = Future.delayed(Duration(seconds: 1)).then(
(value) => 'first future.',
);
final f2 = Future.delayed(Duration(seconds: 2)).then(
(value) => 'second future.',
);
Future.wait([f1, f2]).then((value) => print(value));
//输出:
//[first future., second future.]
等待所有的future完成并返回一个包含所有future执行结果的list,一旦有一个future执行过程中出现了error,最后的结果都不会输出。源码实现:
static Future> wait(Iterable> futures,
{bool eagerError: false, void cleanUp(T successValue)}) {
final _Future> result = new _Future>();
List values; // Collects the values. Set to null on error.
int remaining = 0; // How many futures are we waiting for.
var error; // The first error from a future.
StackTrace stackTrace; // The stackTrace that came with the error.
// Handle an error from any of the futures.
// TODO(jmesserly): use `void` return type once it can be inferred for the
// `then` call below.
handleError(Object theError, StackTrace theStackTrace) {
remaining--;
if (values != null) {
if (cleanUp != null) {
for (var value in values) {
if (value != null) {
// Ensure errors from cleanUp are uncaught.
new Future.sync(() {
cleanUp(value);
});
}
}
}
values = null;
if (remaining == 0 || eagerError) {
result._completeError(theError, theStackTrace);
} else {
error = theError;
stackTrace = theStackTrace;
}
} else if (remaining == 0 && !eagerError) {
result._completeError(error, stackTrace);
}
}
try {
// As each future completes, put its value into the corresponding
// position in the list of values.
for (var future in futures) {
int pos = remaining;
future.then((T value) {
remaining--;
if (values != null) {
values[pos] = value;
if (remaining == 0) {
result._completeWithValue(values);
}
} else {
if (cleanUp != null && value != null) {
// Ensure errors from cleanUp are uncaught.
new Future.sync(() {
cleanUp(value);
});
}
if (remaining == 0 && !eagerError) {
result._completeError(error, stackTrace);
}
}
}, onError: handleError);
// Increment the 'remaining' after the call to 'then'.
// If that call throws, we don't expect any future callback from
// the future, and we also don't increment remaining.
remaining++;
}
if (remaining == 0) {
return new Future.value(const []);
}
values = new List(remaining);
} catch (e, st) {
// The error must have been thrown while iterating over the futures
// list, or while installing a callback handler on the future.
if (remaining == 0 || eagerError) {
// Throw a new Future.error.
// Don't just call `result._completeError` since that would propagate
// the error too eagerly, not giving the callers time to install
// error handlers.
// Also, don't use `_asyncCompleteError` since that one doesn't give
// zones the chance to intercept the error.
return new Future.error(e, st);
} else {
// Don't allocate a list for values, thus indicating that there was an
// error.
// Set error to the caught exception.
error = e;
stackTrace = st;
}
}
return result;
}
整体来说很简单,可以看到内部维护了一个future的列表,当全部执行完毕后返回结果,如果出错就直接返回错误信息。
Future.microtask
看起一个microtask的简单写法,用法同scheduleMicrotask,内部也是通过scheduleMicrotask来实现的,直接看源码吧:
factory Future.microtask(FutureOr computation()) {
_Future result = new _Future();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
异常处理
async、await的异常处理
async、await的异常处理和同步代码的异常处理是一样的,采用try catch的方法,同样也是可以使用finally的:
main() {
errorDemo();
}
void errorDemo() async {
try {
await Future.delayed(Duration(seconds: 1)).then(
(value) => throw '####Error####',
);
} catch (e) {
print(e);
} finally {
print('finally compeleted.');
}
}
// 输出:
// ####Error####
// finally compeleted.
Future API 的异常处理
方法简介
Future api提供两种方式来处理异常,一个是Future
里的onError回调,另一个是Future
。
-
使用onError回调
main() { Future.delayed(Duration(seconds: 1), () { throw '#####Error#####'; }).then(print, onError: (error) { print('has error'); }).whenComplete(() => print('has complete!')); } // 输出: // has error // has complete!
可以看到并没有报错,而是按照预期依此调用了onError、whenCompelte(相当于try catch里的 finally)回调。这有一个小细节需要注意的是,onError和onValue是同级的,当onValue里面发生异常时是不会走同级的onError回调的,onValue和onError是服务于一个future的。
-
使用catchError
Future.delayed(Duration(seconds: 1), () { throw '#####Error#####'; }).catchError((error){ print('has error'); }); // 输出: // has error
catchError和onError回调的用法差不多,最后也是输出了has error。看他的api,发现还有一个叫做test的可选命名参数,它是做什么的呢?只有当test返回true的时候才会走catchError回调,返回为false时会直接报错。他的入参是error对象,所以可以在test里面做一些特殊处理,比如error类型的过滤等等,默认情况下test始终返回true。
main() { Future.delayed(Duration(seconds: 1), () { throw '#####Error#####'; // 1 }).catchError((String error) { print('has error'); }, test: (error) { if (error is String) { return true; } return false; }); }
可以把标1的地方改成
throw 1
再运行下试试效果。
onError和catchError的优先级问题
main() {
Future.delayed(Duration(seconds: 1), () {// 1
throw '##### Error #####'; // 2
}).then(print, onError: (error) { // 3
print('onError callback'); // 4
}).catchError((error) {
print('catchError callback');
});
}
// 输出:
// onError callback.
运行完delayed方法后,会返回一个Future,叫他Future1,标记 3 处的onError里的回调只对Future 1负责,Future 1爆了异常所以会走到onError回调里,也就是标记 4 的地方,一旦onError捕获了异常就不会再去执行catchError里的代码。如果说标 3 的地方并没有设置onError回调,才会往catchError回调里走。
链式调用的异常处理
链式调用中,一旦某个环节爆了异常,下面的每一个节点都会返回相同的错误,直到遇到catchError回调或者被下面的某个节点的onError回调拦截,直接看官网的一个例子吧:
Future one() => new Future.value("from one");
Future two() => new Future.error("error from two");
Future three() => new Future.value("from three");
Future four() => new Future.value("from four");
void main() {
one() // Future completes with "from one".
.then((_) => two()) // Future completes with two()'s error.
.then((_) => three()) // Future completes with two()'s error.
.then((_) => four()) // Future completes with two()'s error.
.then((value) => Future.value(1)) // Future completes with two()'s error.
.catchError((e) {
print("Got error: ${e.error}"); // Finally, callback fires.
return 42; // Future completes with 42.
}).then((value) {
print("The value is $value");
});
}
// Output of this program:
// Got error: error from two
// The value is 42
同步、异步混合异常处理
无论是async、await,还是Future,都是异步执行的,上面介绍的错误处理方式只能处理异步的异常,那么同步代码和异步代码混合的情况下该怎么处理异常呢?看下面的例子:
void main() {
getLegalErpLength().then(print).catchError((error) {
print('error happen!');
});
}
Future getLegalErpLength() {
final erp = findErpFromDb(); // 1
return Future.value(erp).then(parseErp);
}
int parseErp(String erp) {
return erp.length;
}
String findErpFromDb() {
throw 'unknown error';
}
//输出:
Unhandled exception:
unknown error
#0 findErpFromDb
#1 getLegalErpLength
#2 main
#3 _startIsolate. (dart:isolate-patch/isolate_patch.dart:301:19)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
Process finished with exit code 255
getLegalErpLength是一个异步方法,里面调用了同步方法:findErpFromDb,当findErpFromDb出现异常的时候getLegalErpLength时无法进行捕获的,所以直接输出了上面的错误栈。那如果想捕获findErpFromDb的异常应该怎么写呢?需要引入Future.sync
,修改如下:
Future getLegalErpLength() {
return Future.sync(() {
final erp = findErpFromDb();
return Future.value(erp).then(parseErp);
});
}
// 输出:
// error happen!
使用Future.sync将同步方法和异步方法包装一下,这样就完美的解决了上面的问题。
Future原理简介[1]
-
Future()、Future.delayed()
背后使用的timer来实现,具体的源码可以参考dart sdk中的timer_impl.dart文件。简单来讲,是用一个_TimerHeap的数据结构来存取timer,内部采用堆排序算法对其进行排序,当预先设置的时间到时,有一个eventhandler的东西负责处理设置的回调。
-
Future.value()、Future.microtask()
内部层层调用后,最终调用scheduleMicrotask方法,将其放到microtask queue中执行。内部也维护了一个loop,将每一个回调封装成_AsyncCallbackEntry放到loop中,然后循环执行,直到loop为空。具体可以参考schedule_microtask.dart文件。
创建Future的另外一种方式:Completer
completer可以用来创建future,相比使用future,completer可以自己指定future的完成时机。在开发中,能使用future的地方就直接使用future,它比completer更易读,除非有些情况下拿不到想要的future对象,可以使用completer来替代,比如下面这个获取图片长宽的例子:
class CompleterDemo extends StatelessWidget {
Widget build(BuildContext context) {
Image image = new Image.network('https://img01.e23.cn/2020/0519/20200519022445869.jpg');
Completer completer = new Completer();
image.image.resolve(new ImageConfiguration()).addListener(ImageStreamListener(
(image, synchronousCall) {
completer.complete(image.image);
},
));
return new Scaffold(
appBar: new AppBar(
title: new Text("Image Dimensions Example"),
),
body: new ListView(
children: [
new FutureBuilder(
future: completer.future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return new Text(
'${snapshot.data.width}x${snapshot.data.height}',
style: Theme.of(context).textTheme.display3,
);
} else {
return new Text('Loading...');
}
},
),
image,
],
),
);
}
}
Stream
相比future,stream可以用来处理数据流。Stream分为两种,一种是single subscription streams,只允许监听一次,另外一种是Broadcast streams,可以允许监听多次。直接看Stream的API你会发现,这东西和RxJava实在是太像了,一些API的名字和用法几乎差不多,所以这里不再对Stream的常用API进行梳理,感兴趣的直接去看官方文档吧,对RxJava有了解的同学学起来是很容易的。
创建Stream
-
Stream自带的一些factory构造函数,比如Stream.fromFuture(Future
future)、Stream.fromIterable(Iterable elements)等等。以fromIterable为例: Stream.fromIterable([1, 2]).listen((event) { print('event is $event'); }); // 输出: // event is 1 // event is 2
-
异步生成器函数:async* and yield(那对应的同步生成器函数使用的是 sync* and yield)
main() async { print('Start...'); await for (int number in generateNumbers(2)) { // 1 print(number); } print('End'); } Stream
generateNumbers(int maxCount) async* { print('yield output: '); for (int i = 0; i < maxCount; i++) { yield i; // 2 } print('yield* output: '); yield* createNumbers(maxCount); // 3 } Stream createNumbers(int maxCount) async* { for (int i = 0; i <= maxCount; i++) { yield i; } } // 输出: // Start... // yield output: // 0 // 1 // yield* output: // 0 // 1 // 2 // End 需要注意两点:
1、异步生成器的产物Stream可以使用await for进行遍历,await for必须实在async标记的函数中才会有效,另外同步生成器的产物Iterable可以使用for进行遍历。
2、代码中标记2的地方使用的是 yield,标记3的地方使用的是yield*,他俩的区别是一个生成单个值,一个生成一串值,也就是一个Stream。
-
使用StreamController创建Stream
使用StreamController创建Stream可以在任何地方、任何时间添加event并处理event,但是和async相比要复杂的多,最重要的一点是async创建的Stream不会马上执行,当第一个注册者注册的时候才会执行,但是利用StreamController创建的Stream则不然,看下面的代码:
main() async { var counterStream = timedCounter(const Duration(seconds: 1), 5); await Future.delayed(Duration(seconds: 3), () { print('3 seconds has gone.'); }); counterStream.listen((value) { print('$value -- ${DateTime.now()}'); }); } Stream
timedCounter(Duration interval, [int maxCount]) { var controller = StreamController (); int counter = 0; void tick(Timer timer) { counter++; controller.add(counter); // Ask stream to send counter values as event. if (maxCount != null && counter >= maxCount) { timer.cancel(); controller.close(); // Ask stream to shut down and tell listeners. } } Timer.periodic(interval, tick); // BAD: Starts before it has subscribers. return controller.stream; } // 输出: 3 seconds has gone. 1 -- 2020-05-19 21:52:20.843943 2 -- 2020-05-19 21:52:20.847416 3 -- 2020-05-19 21:52:20.847438 4 -- 2020-05-19 21:52:21.831694 5 -- 2020-05-19 21:52:22.828021
可以看到前三个几乎是同时输出的,这是因为StreamController一旦创建Stream,里面的代码就开始执行,生成的数据会缓存下来,当有注册的时候立马把缓存输出给监听者,为了避免这种情况,可以使用StreamController各种回调方法进行限制,上面的代码修改如下:
Stream timedCounter(Duration interval, [int maxCount]) {
StreamController controller;
int counter = 0;
Timer timer;
void tick(_) {
counter++;
controller.add(counter);
if (maxCount != null && counter >= maxCount) {
timer.cancel();
controller.close();
}
}
void startTimer() {
timer = Timer.periodic(interval, tick);
}
controller = StreamController(
onListen: () {
startTimer();
},
);
return controller.stream;
}
//输出:
3 seconds has gone.
1 -- 2020-05-19 22:02:32.488114
2 -- 2020-05-19 22:02:33.479319
3 -- 2020-05-19 22:02:34.477549
4 -- 2020-05-19 22:02:35.476372
5 -- 2020-05-19 22:02:36.481389
StreamController还有onCancel、onPause、onResume三个回调,用来处理取消、暂停、继续等操作。