Dart是单线程语言,但是请求网络,数据操作和IO操作等耗时操作需要异步,否则可能会导致无响应。Isolate机制是Dart中的异步机制。与Java中的线程不同,Isolate中的线程(可以先称之为线程)是内存隔离的(像进程一样的线程),而Java中的线程是内存共享的。
Dart中的main函数启动时会启动一个进程(可先称之为主线程,或者主isolate)。使用Isolate.spawn可以创建一个新的Isolate。
import 'dart:io';
import 'dart:isolate';
int i;
void main() {
i = 2;
Isolate.spawn(entryPoint, "hello");//开启一个子Isolate
}
//entryPoint相当于在一个新的子线程中执行
void entryPoint(Stirng message) {
print(i); //输出为null,而非2,也说明了主isolate与子isolate内存隔离,无法共享
sleep(Duration(seconds: 10)); //子isolate延迟,不会影响主isolate
print(message);
}
消息接收器和消息发送器即ReceivePort和SendPort,它们在每个isolate中均存在,并且只有同一个isolate中的ReceivePort才能接受到其SendPort发送的消息并进行处理。
void main() {
//创建一个消息接收器
var receivePort = new ReceivePort();
//从消息接收器读取消息
//消息接收器添加监听,监听接收的消息是发送器发送过来的
receivePort.listen((t) {
print(t);
});
//发送消息
receivePort.sendPort.send("1");
receivePort.sendPort.send("2");
receivePort.sendPort.send(1);
//使用完毕关闭消息接收器
receivePort.close();
}
主isolate和子isolate之间如何通信? -- 通过SendPort和ReceivePort
1、主线程中创建消息接收器
void main() {
//创建一个消息接收器
var receivePort = new ReceivePort();
//开启一个子isolate,并将主isolate消息接收器中的发送器发给子isolate
Isolate.spawn(entryPoint, receivePort.sendPort);
//消息接收器添加监听
receiverPort.listen((t) {
print(t); //主isolate接收到子isolate中发送的消息
});
}
void entryPoint(SendPort sendPort) {
sendPort.send("111"); //在子isolate中使用发送器发送消息
}
2、子线程中创建消息接收器
void entryPoint(SendPort sendPort) {
var receivePort = new ReceivePort();
var sendPort2 = receivePort.sendPort; //子isolate的消息发送器
sendPort.send(sendPort2);
receivePort.listen((t) { //子isolate接收主isolate发送的消息
});
}
void main() {
var receivePort = new ReceivePort();
Isolate.spawn(entryPoint, receivePort.sendPort);
receivePort.listen((t) {
print("接收到其他isolate发送过来的消息!");
//主isolate中接收到子isolate中的消息发送器
if(t is SendPort) {
//to do
}
});
}
Dart中每一个isolate都相当于一个独立的执行环境。消息驱动机制存在于Dart的isolate中,每一个isolate中都有一个独立的完整的event-loop任务队列。这种消息驱动机制与Android中的消息驱动机制类似,不同的是,Dart中有两个消息队列,一个是普通事件队列(event queue),另一个是微任务队列(microtask queue)。Dart执行完main函数之后,就会通过loop开始执行两个任务队列中的事件。Loop首先检查微任务队列,依次执行微任务队列中的event;当微任务队列执行完之后,检查event queue队列依次执行,在执行event queue过程中,每执行完一个event就再检查一次微任务队列。故微任务队列的优先级较高,我们可以利用微任务队列进行消息插队。
void main() {
var receivePort = new ReceivePort();
receivePort.listen((t) {
print(t);
});
//使用Future.microtask往微任务队列中提交一个消息
Future.microtask(() {
print("微任务执行--1");
});
receivePort.sendPort.send("发消息给消息接收器--1");
Future.microtask(() {
print("微任务执行--2");
});
receivePort.sendPort.send("发消息给消息接收器--2");
Future.microtask(() {
print("微任务执行--3");
});
receivePort.sendPort.send("发消息给消息接收器--3");
}
上述示例代码会先打印微任务执行,再打印普通任务消息,说明微任务队列的优先级较高,输出为
微任务执行--1
微任务执行--2
微任务执行--3
发消息给消息接收器--1
发消息给消息接收器--2
发消息给消息接收器--3
因为Dart是单线程模型,消息队列在同一isolate中。如果微任务队列执行后延迟了几秒,则后续的任务均会延迟执行。
我们平时执行的任务大部分是在event-queue中。Dart中存在插队机制,如果我们希望某个任务优于其他任务执行,可以使用微任务队列进行插队执行。
void main() {
var receivePort = new ReceivePort();
receivePort.listen((t) {
print(t);
Future.microtask(() {
print("微任务执行");
});
});
receivePort.sendPort.send("发消息给消息接收器1");
receivePort.sendPort.send("发消息给消息接收器2");
receivePort.sendPort.send("发消息给消息接收器3");
//main函数执行延迟10s,消息队列执行也会延迟10s,也即任务队列是在main函数执行完毕之后执行的
sleep(Duration(second: 10)); //main函数不是由消息队列执行,此行未加入到消息队列中
}
上述示例代码利用微任务队列实现插队,执行结果为
发消息给消息接收器1
微任务执行
发消息给消息接收器2
微任务执行
发消息给消息接收器3
微任务执行
在Flutter编程中使用更多的是Future和Stream。
在Java开发中进行并发编程时,会用到Future来获取异步任务执行的结果。在Dart中我们执行一个任务也可以使用Future来完成。Future表示未来执行的任务,是在任务队列中提取任务来执行。Future默认执行的event-queue普通任务队列中的任务,也可以提交任务到微任务队列。Future提供的api中绝大部分是用来执行普通任务的,如:wait(), forEach(), doWhile(), delayed(), any(), error()...,但可以通过Future.microtask()提交一个微任务。
//Future基本使用
void main() {
Future f = Future.delayed(Duration(seconds: 3)); //将延迟任务放到任务队列中
f.then((t) { //通过then可以获取future的执行结果
print(t);
});
}
对于网络请求或文件操作,java一般将其放在一个子线程中去执行。而在Dart中进行文件IO操作是十分方便的,直接调用对应的异步方法即可。如File相关函数readAsString()、readAsBytes()、readAsLines()等,另外还有对应的同步方readAsStringSync()、readAsBytesSync()、readAsLinesSync()等。例如:
void main() {
new File(r"c:\test.txt").readAsString() //读取文件中内容,返回future类对象
.then((String s) { //这里获取结果类型确认为String
print(s); //打印读取的字符串
})
.catchError((s, e) { //捕获异常
});
Future.delayed(Duration(seconds: 10)); //延迟任务。上一个任务异常会导致后续的任务无法执行
}
then方法返回后可以得到Future任务执行的结果并且返回一个新的Future对象:
void main() {
Future<int> then = new File(r"c:\test.txt").readAsString().then((String s) {
print(s); //打印读取的字符串
return 10; //这里添加返回值
});
then.then((int i) {
print(i); //这里打印结果为10
});
}
或者直接使用链式调用的方式写成如下形式:
void main() {
Future<int> then = new File(r"c:\test.txt").readAsString().then((String s) {
print(s); //打印读取的字符串
return 10;
}).then((int i) {
print(i); //10
});
}
这里和rxjava中的写法相似,也是返回一个同类型的对象,使用链式调用的方式。
使用Future.then可以完成有序任务的执行,即前一个任务执行完毕,后一个任务根据前一个任务的结果执行后续的操作。如果要实现一组任务执行完之后再统一执行后续的操作,可以使用Future.wait():
void main() {
Future.wait([Future.delayed(seconds: 5),
Future.delayed(seconds: 10)]).then((_) {
print(1);
});
}
Stream:
Stream在Dart中也经常出现,表示发出的一系列异步数据,同样可以用于异步操作。Future表示稍后获得的一个数据,所有异步的操作返回值均用Future表示。它与Future的区别在于,Future表示一次异步获得的数据,Stream表示需要多次异步操作获得的结果。Stream的好处是处理过程中内存占用较小。
void main() {
var stream = new File(r"c:\test.txt").openRead(); //返回Stream<List<int>>,泛型类型是<List<int>>
stream.listen((s) {
print(1); //如果文件较大,会读取多次,但Future会一次性读取完毕
});
}
Stream.listen()其实就是去订阅这个Stream,它返回一个StreamSubscription类型的订阅者对象。订阅者提供了一些方法方便我们执行订阅的相关操作,如cancel()用于取消监听读取过程,这样listen中就无法接收到任何信息了,onData()可以重置listen方法,onDone()监听读取完毕,pause()和resume()可以停止和恢复监听等。
void main() {
StreamSubscription<List<int>> listen = new File(r"c:\test.txt").openRead()
.listen((List<int> bytes) {
print("stream执行");
});
listen.onData((_) {
print("替换listen");
});
listen.onDone(() {
print("结束");
});
listen.pause(); //暂停,如果没有继续,程序退出
listen.resume();
}
Stream广播模式:
Stream有两种订阅模式:单订阅模式和多订阅模式。Stream默认是单订阅模式,即只能有一个订阅者,若有多个订阅者会报错。前述使用的都是单订阅模式。而广播模式可以有多个订阅者,通过Stream.asBroadcastStream()可以将一个单订阅模式的stream转换为一个多订阅模式的stream,通过isBroadcast属性可以判断当前stream所属的模式。
void main() {
var stream = new File(r"c:\test.txt").openRead();
stream.listen((List<int> bytes) {
print("stream执行");
});
//如下会报错,单订阅模式只能有一个订阅者
//stream.listen((_) {
// print("stream执行2");
// });
//如下会报错 ,单订阅的stream如果已经添加了订阅者,则不能再转换成广播模式了
//var streamBroadcast = stream.asBroadcastStream();
var streamBroadcast = new File(r"c:\test.txt").openRead().asBroadcastStream();
streamBroadcast.listen((_) {
print("订阅者1");
});
streamBroadcast.listen((_) {
print("订阅者2");
});
streamBroadcast.listen((_) {
print("订阅者3");
});
}
Stream的广播模式除了通过上述使用单订阅模式转换的方式,还可以使用StreamController直接创建广播:
void main() {
//创建一个广播,对stream进行管理
var streamController = StreamController.broadcast();
//添加一个事件 即发送一条广播
streamController.add("11");
//监听
//先发出事件再订阅,无法收到通知
streamController.stream.listen((i) {
print("广播:$i");
});
//使用完关闭
streamController.close();
//--------------------------------------------
var stream = Stream.forIterable([1, 2, 3, 4]);
//单订阅转换的广播消息发出后再监听也是可以收到消息的
var broadcastStream = stream.asBroadcastStream();
broadcastStream.listen((i) {
print("订阅者:${i}");
});
new Timer(Duration(seconds: 3), ()=> {
broadcastStream.listen((i) {
print("订阅者2:${i}"); //也可以收到消息
});
});
}
广播模式下无法接收到订阅前的消息,而通过单订阅模式转换的广播先发送再注册是可以接收到消息的,利用此特性可以实现粘性广播。
StreamController实现了StreamSink,而StreamSink实现了EventSink,EventSink有add,addError,close等方法,EventSink实现了sink,EventSink和Sink均是抽象类,Sink只有add(发出一个广播)和close(关闭)两个方法。
async和await:
这是Dart中的两个关键字,使用async和await的代码表示是异步执行的。
//async表示这是一个异步方法,异步方法只能返回void和Future
//await必须在async方法中使用
Future<String> readFile() async {
//await 等待future执行完之后再执行后续的代码,即实现了同步的效果,其作用类似then方法,可以避免then方法回调地狱
String content = await new File("/path/test.txt").readAsString();
String content2 = await new File("/path/test.txt").readAsString();
String content3 = await new File("/path/test.txt").readAsString();
//自动转换为Future
return content;
}
void main() {
new File("/path/test.txt").readAsString(); //这里读取文件不是同步的。
new File("/path/test.txt").readAsString();
}
来源:CSDN
作者:xj_hsym
链接:https://blog.csdn.net/hsym321/article/details/103083640