美文网首页FlutterFlutter圈子Flutter
flutter“多线程”isolate小学生级理解

flutter“多线程”isolate小学生级理解

作者: 七分小熊猫 | 来源:发表于2020-03-29 17:25 被阅读0次

    我们小学二年级就学过:

    Dart 是单线程的并且 Flutter 依赖于 Dart。

    但是如果我们要在主线程做一些计算量大的操作,就必然会阻塞线程,使UI更新卡顿甚至卡死。那怎么办呢?

    好消息是 Dart 为我们提供了 isolate,isolate 跟线程差不多,他是 Dart 中的线程。

    isolate 与线程的区别就是线程与线程之间是共享内存的,而 isolate 和 isolate 之间是不共享的,所以叫 isolate (隔离)。

    在flutter 里面主线程就是主 isolate 。如果我们要进行一些大计算量的操作就应该启动一个新的 isolate。

    那么应该如何来开启呢?在此之前我想讲个故事。

    小红与小蓝的故事

    有个舞者叫小红,她正在给观众跳舞。

    跳舞的小红

    但是观众却要求她一边跳舞一边计算一个数字里面有多少个偶数。于是。。。

    异步计算

    这那行啊!你必须给我一边跳一边算,算的时候不能停下来!

    于是小红没办法,决定在异世界召唤一个小蓝来帮她计算。

    image

    但是小红和小蓝被异世界的屏障隔离,她们也没有思想共通的超能力。只能在召唤的同时传送一个包裹给小蓝

    小蓝被召唤出来后收到包裹,打开后里面是要计算的数字,就开始计算,但是计算后要怎么把结果告诉小红呢?

    上帝做了一个约定,在小红召唤小蓝的时候,会变一个传送装置(传送装置可以用来接收包裹,还可以生成一个专属发送器)。然后把发送器传送给小蓝。

    当小蓝被召唤出来后,打开包裹,里面是一个发送器,然后小蓝自己也变一个传送装置,生成一个发送器,然后用小红的发送器把小蓝的发送器发送给小红。发送出去后就坐在传送装置旁边等包裹。

    当小红收到小蓝的发送器后就把小蓝的发送器存起来。

    当有观众要求小红计算时,就分神一边跳舞,一边生成一个临时传送装置,把要计算的数字和临时发送器打包成一个包裹,然后通过小蓝的发送器发给小蓝,等传送装置出结果。因为不用自己算了,只是等,所以跳舞的时候线条也流畅了,动作也优美了。

    说回小蓝这边,小蓝看到传送装置出现了一个包裹,里面是一个临时发送器,还有一个数字。于是小蓝就开始计算。算好了就用临时发送器把数字发送给小红。

    小红收到结果后就告诉观众,那个数字有多少个偶数。

    故事结束,第一次尝试这样的风格,可能写得有点烂,不过结合代码来看的话,应该还是挺容易理解的。

    代码实践

    首先我们先让小红跳起舞来。

      @override
      void initState() {
        controller =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
        controller.repeat();
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                AnimatedBuilder(
                    animation: animation,
                    child: Text(
                      '小红',
                      style: TextStyle(fontSize: 30, color: Colors.red),
                    ),
                    builder: (context, child) {
                      return Transform.rotate(
                        angle: animation.value,
                        child: child,
                      );
                    }),
              ],
            ),
          ),
        );
      }
    

    接下来让小红计算一个数字里面有多少个偶数。

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              AnimatedBuilder(
                  animation: animation,
                  child: Text(
                    '小红',
                    style: TextStyle(fontSize: 30, color: Colors.red),
                  ),
                  builder: (context, child) {
                    return Transform.rotate(
                      angle: animation.value,
                      child: child,
                    );
                  }),
              Padding(
                padding: EdgeInsets.only(top: 16),
                child:
                    RaisedButton(onPressed: count, child: Text('异步计算偶数的个数')),
              ),
              Text(result)
            ],
          ),
        ),
      );
    }
    
    int getRandom() {
      int a =  Random().nextInt(100);
      return a + 1000000000;
    }
    
    // 异步计算
    count() async {
      int random = getRandom();
      int r = countEven(random);
      setState(() {
        this.result = '${random.toString()}有${r.toString()}个偶数';
      });
    }
    
    //计算偶数的个数
    int countEven(num num) {
      int count = 0;
      while (num > 0) {
        if (num % 2 == 0) {
          count++;
        }
        num--;
      }
      return count;
    }
    
    

    这就是效果

    异步计算

    定义 isolate

    我愿称之为召唤小蓝。

    首先我们要知道两个类:

    ReceivePort
    SendPort
    

    ReceivePort 就是故事中的传送装置,而 SendPort 则是发送器。

    我们可以通过以下方式创建传送装置和对应的发送器

    ReceivePort receive = ReceivePort();
    SendPort sender = receive.sendPort;
    

    好的,知道这些就行了。接下来我们定义小蓝。

    // 消息包裹,用来存临时发送器和消息
    class MessagePackage {
      SendPort sender; // 临时发送器
      dynamic msg; // 消息
    
      MessagePackage(this.sender, this.msg);
    
    }
    
    // 我是小蓝,负责计算偶数的个数,我必须是顶级函数
    blueCounter(SendPort redSendPort) {
      // 创建小蓝的传送装置
      ReceivePort blueReceivePort = ReceivePort();
      // 用小红的发送器把小蓝的发送器发送出去
      redSendPort.send(blueReceivePort.sendPort);
      // 监听小蓝的传送装置,等待小红叫小蓝计算
      blueReceivePort.listen((package) {
        // 这里的msg是dynamic,需要转换成 MessagePackage 类,上面自己定义的包裹封装类
        MessagePackage _msg = package as MessagePackage;
        // 小蓝开始计算
        int r = countEven(_msg.msg as num);
        // 计算好了用小红的临时发送器告诉小红
        _msg.sender.send(r);
      });
    }
    
    

    创建isolate

    工具人小蓝定义好了,我们去初始化(召唤)一下小蓝。

    // 创建isolate
    void createIsolate() async {
      // 创建小红的接收器,用来接收小蓝的发送器
      ReceivePort redReceive = ReceivePort();
      // 创建 isolate, 并且把小红的发送器传给小蓝
      isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
      // 等待小蓝把发送器发送给小红
      blueSender = await redReceive.first;
      // 不用了记得关闭接收器
      redReceive.close();
    }
    
    
    @override
    void initState() {
      controller =
          AnimationController(duration: Duration(seconds: 3), vsync: this);
      animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
      controller.repeat();
      // 在initState中初始化isolate
      createIsolate();
      super.initState();
    }
    

    现在小蓝已经被召唤了出来,并且和小红建立了通信。

    使isolate 开始计算

    接下来我们就让小红开始计算吧。

    
    @override
    Widget build(BuildContext context) {
      ...
      Padding(
          padding: EdgeInsets.only(top: 16),
          child: RaisedButton(
              onPressed: isolateCount, child: Text('isolate计算偶数的个数')
          ),
      ),
      ...
    }
    
    // 开启isolate计算
    isolateCount() async {
      // 获取要计算的数字
      int random = getRandom();
      // 创建一个临时传送装置
      ReceivePort _temp = ReceivePort();
      // 用小蓝的发送装置发送一个消息包裹,里面是临时传送装置的发送器和要计算的数字
      blueSender.send(MessagePackage(_temp.sendPort, random));
      // 等待临时传送装置返回计算结果
      int r = await _temp.first;
      // 不用了记得关闭临时接收器
      _temp.close();
      // 把计算结果告诉观众
      setState(() {
        this.result = '${random.toString()}有${r.toString()}个偶数';
      });
    }
    

    需要注意的是当使用完了 isolate 记得要销毁。

    
    @override
    void dispose() {
      // 销毁 isolate
      isolate?.kill(priority: Isolate.immediate);
      super.dispose();
    }
    

    OK,到这里相信你已经看懂并会使用 isolate 了。
    我们来看看效果图。

    isolate计算

    使用 computed

    到这里还没完,也许你会觉得太麻烦了。是的这样用 isolate 太麻烦了,isolate 被设计成可以多次输入输出,而我们做这个计算只有一次输入和输出,那么我们就可以用 flutter 为我们提供的 computed 来完成计算操作,它是对 isolate 的一个封装。下面看看怎么用吧!敲简单的。

    import 'dart:isolate';
    
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'dart:math';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'isolate Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'isolate Demo'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
      String result = '';
      SendPort blueSender;
      Isolate isolate;
    
      @override
      void initState() {
        controller =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
        controller.repeat();
        // 在initState中初始化isolate
        createIsolate();
        super.initState();
      }
    
      @override
      void dispose() {
        // 销毁 isolate
        isolate?.kill(priority: Isolate.immediate);
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                AnimatedBuilder(
                    animation: animation,
                    child: Text(
                      '小红',
                      style: TextStyle(fontSize: 30, color: Colors.red),
                    ),
                    builder: (context, child) {
                      return Transform.rotate(
                        angle: animation.value,
                        child: child,
                      );
                    }),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: RaisedButton(onPressed: count, child: Text('异步计算偶数的个数')),
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: RaisedButton(
                      onPressed: isolateCount, child: Text('isolate计算偶数的个数')),
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: RaisedButton(
                      onPressed: computeCount, child: Text('compute计算偶数的个数')),
                ),
                Text(result)
              ],
            ),
          ),
        );
      }
    
      // 获取随机数
      int getRandom() {
        int a = Random().nextInt(100);
        return a + 1000000000;
      }
    
      // 异步计算
      count() async {
        int random = getRandom();
        int r = countEven(random);
        setState(() {
          this.result = '${random.toString()}有${r.toString()}个偶数';
        });
      }
    
      // 创建isolate
      void createIsolate() async {
        // 创建小红的接收器,用来接收小蓝的发送器
        ReceivePort redReceive = ReceivePort();
        // 创建 isolate, 并且把小红的发送器传给小蓝
        isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
        // 等待小蓝把发送器发送给小红
        blueSender = await redReceive.first;
        // 不用了记得关闭接收器
        redReceive.close();
      }
    
      // 利用compute计算
      computeCount() async {
        int random = getRandom();
        // compute 的回调函数必须是顶级函数或者static函数
        int r = await compute(countEven, random);
        setState(() {
          this.result = '${random.toString()}有${r.toString()}个偶数';
        });
      }
    
      // 开启isolate计算
      isolateCount() async {
        // 获取要计算的数字
        int random = getRandom();
        // 创建一个临时传送装置
        ReceivePort _temp = ReceivePort();
        // 用小蓝的发送装置发送一个消息包裹,里面是临时传送装置的发送器和要计算的数字
        blueSender.send(MessagePackage(_temp.sendPort, random));
        // 等待临时传送装置返回计算结果
        int r = await _temp.first;
        _temp.close();
        // 把计算结果告诉观众
        setState(() {
          this.result = '${random.toString()}有${r.toString()}个偶数';
        });
      }
    }
    
    // 消息包裹,用来存临时发送器和消息
    class MessagePackage {
      SendPort sender; // 临时发送器
      dynamic msg; // 消息
    
      MessagePackage(this.sender, this.msg);
    }
    
    // 我是小蓝,负责计算偶数的个数,我必须是顶级函数
    blueCounter(SendPort redSendPort) {
      // 创建小蓝的传送装置
      ReceivePort blueReceivePort = ReceivePort();
      // 用小红的发送器把小蓝的发送器发送出去
      redSendPort.send(blueReceivePort.sendPort);
      // 监听小蓝的传送装置,等待小红叫小蓝计算
      blueReceivePort.listen((package) {
        // 这里的msg是dynamic,需要转换成 MessagePackage 类,上面自己定义的包裹封装类
        MessagePackage _msg = package as MessagePackage;
        // 小蓝开始计算
        int r = countEven(_msg.msg as num);
        // 计算好了用小红的临时发送器告诉小红
        _msg.sender.send(r);
      });
    }
    
    //计算偶数的个数,此函数需要大量的计算资源和时间
    int countEven(num num) {
      int count = 0;
      while (num > 0) {
        if (num % 2 == 0) {
          count++;
        }
        num--;
      }
      return count;
    }
    

    相关文章

      网友评论

        本文标题:flutter“多线程”isolate小学生级理解

        本文链接:https://www.haomeiwen.com/subject/deyuuhtx.html