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

参考

相关推荐
王喆15 小时前
跨平台全屏效果实现方案:HarmonyOS、Android与iOS实践总结
flutter·harmonyos
Tee xm17 小时前
清晰易懂的 Flutter 卸载和清理教程
linux·windows·flutter·macos
leluckys20 小时前
flutter 专题 七十三Flutter打包未签名的ipa
flutter
张风捷特烈2 天前
Flutter 伪3D绘制#03 | 轴测投影原理分析
android·flutter·canvas
马拉萨的春天2 天前
flutter 项目结构目录以及pubspec.ymal等文件描述
flutter
bst@微胖子3 天前
Flutter项目之登录注册功能实现
开发语言·javascript·flutter
小墙程序员3 天前
Flutter 教程(十一)多语言支持
flutter
无知的前端3 天前
Flutter 一文精通Isolate,使用场景以及示例
android·flutter·性能优化
yidahis3 天前
Flutter 运行新建项目也报错?
flutter·trae