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() 函数只能传递一个参数,它的返回值也只有一个。

参考

相关推荐
ak啊1 小时前
Flutter项目架构设计方案
flutter
JarvanMo4 小时前
在Dart泛型中应该优先使用dynamic还是Object?
前端·flutter·dart
恋猫de小郭4 小时前
Flutter 在 Dart 3.8 开始支持 Null-Aware Elements 语法,自动识别集合里的空元素
android·前端·flutter
恋猫de小郭7 小时前
Flutter Widget IDE 预览新进展,开始推进落地发布
android·前端·flutter
Ya-Jun12 小时前
常用第三方库:flutter_boost混合开发
android·flutter·ios
技术蔡蔡19 小时前
全面解读Flutter状态管理框架signals使用,知其然和所以然
flutter·dart
pengyu20 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
肥肥呀呀呀20 小时前
flutter 小知识
flutter
玫瑰花开一片一片1 天前
Flutter IOS 真机 Widget 错误。Widget 安装后系统中没有
flutter·ios·widget·ios widget