Flutter 教程(十二)异步编程

不同于 Java、C++ 使用多线程来实现异步编辑,Dart 没有多线程的概念,它是通过事件循环和隔离来实现异步编程的。

事件循环和隔离

事件循环的原理就是"主线程"循环拉取队列中的事件来执行,如果事件有IO操作或者延时等操作,则会将该事件挂起,并执行下一个任务或者事件。事情循环类似于 kotlin 的指定了线程调度的协程。其运作原理,如下图所示:

可以看到事件循环有两个队列,一个是微任务队列(Microtask queue),另一个是事件队列(Event queue)。其中微任务队列包含Flutter内部的微任务,主要通过scheduleMicrotask来调度;事件队列包含外部事件,例如I/O、Timer和绘制事件等。

为什么 Dart 采用事件循环的单线程异步而不是多线程来实现异步?

  1. 对于移动/桌面客户端,大多数的需求都是网络、数据库访问等 io 密集型 的任务,使用单线程异步机制就足以满足大部分需求。
  2. 减少多线程带来的复杂性问题
  3. 避免了线程创建和销毁的开销,提高了性能。

Dart 如何处理 cpu 密集型任务?

Dart 的单线程异步能方便处理 io 密集型 的任务,但是对于 cpu 密集型任务则需要使用 隔离(isolate)机制来处理

单线程异步

关于 Dart 的单线程异步,主要有 Future、async和await、Stream 三部分知识点。下面分别介绍

Future

Future 表示一个异步操作的最终完成(或失败)及其结果值的表示。比如说,我们往文件写入文字就会返回一个 Future 对象,如下所示:

dart 复制代码
File file = File(filePath);
Future<File> futureFile = file.writeAsString("hello world");

可以看到 Future 就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。一些常见的 Future 示例如下:

  • Future 链式调用
scss 复制代码
Future.delayed(Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");
}).then((data){
   //执行成功会走到这里 
   print(data);
}).catchError((e){
   //执行失败会走到这里   
   print(e);
}).whenComplete((){
   //无论成功或失败都会走到这里
});
  • Future 等待多个任务完成
scss 复制代码
Future.wait([
  // 2秒后返回结果  
  Future.delayed(Duration(seconds: 2), () {
    return "hello";
  }),
  // 4秒后返回结果  
  Future.delayed(Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});

async、await

  • async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用 then 方法添加回调函数。
  • await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。

代码示例如下:

scss 复制代码
task() async {
   try{
    String id = await login("alice","******");
    String userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
    //执行接下来的操作   
   } catch(e){
    //错误处理   
    print(e);   
   }  
}

注意:在 Dart 中,async/await 只是一个语法糖,编译器或解释器最终都会将其转化为一个 Future 的调用链。

Stream

Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。代码示例如下:

dart 复制代码
Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){

});

我们还可以使用 Stream 来实现事件流。为了控制事件流,通常使用StreamController来进行管理。例如,为了向事件流中流入数据,StreamController提供了类型为StreamSink的sink属性作为数据的入口,同时StreamController也提供了Stream属性作为数据的出口,如下图所示:

代码示例如下:

scala 复制代码
class StreamPage extends StatefulWidget {
  StreamPage({Key? key, this.title}) : super(key: key);

  final String? title;

  @override
  _StreamState createState() => _StreamState();
}

class _StreamState extends State<StreamPage> {
  final StreamController<int> _streamController=StreamController<int>();
  int _counter=0;

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title ?? ""),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '自动累加的数据',
              style: TextStyle(fontSize: 24),
            ),
            StreamBuilder<int>(
              stream: _streamController.stream,
              initialData: 0,
              builder: (BuildContext context,AsyncSnapshot<int> snapshot){
                return Text(
                  '${snapshot.data}',
                  style: TextStyle(fontSize: 24),
                );
              },
            ),
            FilledButton(
              child: Text(widget.title ?? ""),
              onPressed: () => _streamController.sink.add(++_counter),
            ),
          ],
        ),
      ),
    );
  }
}

隔离(isolate)

所有的 Flutter Dart 代码都是在隔离上运行的,而对应的Android UI主线程在Flutter中被称为主隔离(main isolate)。我们可以使用 spawnUrispawn 方法创建隔离并执行指定任务。其中 spawnUri 方法,基于给定库的URI来产生一个隔离;而 spawn 方法,根据当前隔离的根库生成一个隔离。代码示例如下:

scss 复制代码
// 用于与另一个隔离通信
final receivePort = new ReceivePort();
Isolate.spawn(_isolate2, receivePort.sendPort);

// 具体的任务
_isolate2(SendPort replyTo) async {
    ...
}

这些隔离有着自己的内存和单线程控制的运行实体,因此隔离之间是没有共享内存。如果隔离之间需要通信,则需要消息传递。代码示例如下:

scss 复制代码
//主隔离
_main() async {
    //隔离所需要的参数必须要有SendPort,而SendPort又需要ReceivePort来创建
    final receivePort = new ReceivePort();
    //使用Isolate.spawn创建隔离,其中_isolate2是我们自己实现的
    await Isolate.spawn(_isolate2, receivePort.sendPort);
    //发送一个message,这是它的sendPort
    var sendPort = await receivePort.first;
    var message = await sendMessage(sendPort, "你好");
    print("message:$message");//等待消息返回
}
//isolate2
_isolate2(SendPort replyTo) async {
  //创建一个ReceivePort,用于接收消息
  var port = ReceivePort();
  //把它发送给主隔离,以便主隔离可以给它发送消息
  replyTo.send(port.sendPort);
  port.listen((message) {//监听消息,从Port获取
    SendPort send = message[0] as SendPort;
    String str = message[1] as String;
    print(str);
    send.send("应答");
    port.close();
  });
}
//使用Port进行通信,同时接收返回应答
Future sendMessage(SendPort port,String str) {
  ReceivePort receivePort=ReceivePort();
  port.send([receivePort.sendPort, str]);
  return receivePort.first;
}

如果我们只想要请求一次数据,而不需要像上面一样相互通信,则可以使用 compute 方法。这个函数非常简单,它只有两个参数,第一个参数是需要执行耗时任务的方法的名称,第二个参数是前面方法需要传递的参数。代码示例如下

dart 复制代码
var _count;
// 耗时任务
int countEven(int num) {
  int count = 0;
  while (num > 0) {
    if (num % 2 == 0) {
      count++;
    }
    num--;
    print(count);
  }
  return count;
}
_call_compute() async{
  _count = await compute(countEven, 1000000000);
}

需要注意,compute() 函数中运行的方法必须是顶级方法或者是static方法;而且 compute() 函数只能传递一个参数,它的返回值也只有一个。

参考

相关推荐
TE-茶叶蛋13 小时前
Uniapp、Flutter 和 React Native 全面对比
flutter·react native·uni-app
只可远观1 天前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰1 天前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
肥肥呀呀呀1 天前
flutter Stream 有哪两种订阅模式。
flutter
WDeLiang2 天前
Flutter - 集成三方库:日志(logger)
flutter·dart
hudawei9962 天前
flutter缓存网络视频到本地,可离线观看
flutter·缓存·音视频
0wioiw02 天前
Flutter基础()
flutter
肥肥呀呀呀3 天前
flutter 视频通话flutter_webrtc
flutter
明似水3 天前
2025年Flutter项目管理技能要求
flutter
肥肥呀呀呀3 天前
flutter使用命令生成BinarySize分析图
flutter