Flutter第十七章(Isolate 并发)

版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

情感语录: 有人帮你,是你的幸运;无人帮你,是公正的命运。没有人该为你做什么,因为生命是你自己的,你得为自己负责。

欢迎来到本章节,上一章节介绍了Flutter 中的 WebView的使用,知识点回顾 戳这里 Flutter第十六章

本章节来了解下 Flutter 中的多线程(Isolate),我们知道 Flutter 是采用的 Dart 语言进行开发,而Dart 语言是一门单线程模型语言,并没有多线程的概念。这里叫多线程其实并不合理,官方直译应该叫 “隔离器”,因为真正的线程之间是可以有共享内存的,但 Isolate 并不可以,因为Dart没有共享内存的并发,没有竞争性的抢占所以不需要锁,也就不用担心死锁的问题,这也是和线程概念最大的区别。

一、什么是 Isolate?

回答这个问题之前我们先跟进 Isolate 中去看下源码,它位于Dart library dart.isolate 中,因此需要你 import 'dart:isolate'; 导包引入。首先我们来看下源码中对它的描述:

    /**
     * An isolated Dart execution context.
     *
     * All Dart code runs in an isolate, and code can access classes and values
     * only from the same isolate. Different isolates can communicate by sending
     * values through ports (see [ReceivePort], [SendPort]).
     *
     * An `Isolate` object is a reference to an isolate, usually different from
     * the current isolate.
     * It represents, and can be used to control, the other isolate.
     *
     * When spawning a new isolate, the spawning isolate receives an `Isolate`
     * object representing the new isolate when the spawn operation succeeds.
     *
     * Isolates run code in its own event loop, and each event may run smaller tasks
     * in a nested microtask queue.
     *
     * An `Isolate` object allows other isolates to control the event loop
     * of the isolate that it represents, and to inspect the isolate,
     * for example by pausing the isolate or by getting events when the isolate
     * has an uncaught error.
     *
     * The [controlPort] identifies and gives access to controlling the isolate,
     * and the [pauseCapability] and [terminateCapability] guard access
     * to some control operations.
     * For example, calling [pause] on an `Isolate` object created without a
     * [pauseCapability], has no effect.
     *
     * The `Isolate` object provided by a spawn operation will have the
     * control port and capabilities needed to control the isolate.
     * New isolate objects can be created without some of these capabilities
     * if necessary, using the [Isolate.Isolate] constructor.
     *
     * An `Isolate` object cannot be sent over a `SendPort`, but the control port
     * and capabilities can be sent, and can be used to create a new functioning
     * `Isolate` object in the receiving port's isolate.
     */
    class Isolate {
        // ......省略 
    }

这段文档大致意思是说:Isolate 是一个独立的Dart 程序执行环境,所有的 Dart 代码都运行在某一个 isolate 中,在同一个 isolate 中可以访问 类和属性值,在不同的 isolate中是通过 port 发送message进行交流(SendPort(发送端),ReceivePort(接收端))。

一个Isolate对象就是一个isolate(执行环境)的引用,通常不是当前代码所在的isolate,也就是说,当你使用Isolate对象时,你的目的应该是控制其他isolate,而不是当前的isolate。每个 isolate 运行在自己的事件循环(event loop)中,每个事件都可以再其中执行更小的任务。它允许被其他 Isolate 控制事件循环,例如当这个isolate发生未捕获错误时,可以暂停(pause)此isolate或获取(addErrorListener)错误信息。

简单粗暴的说:Isolate 就是一个独立运行的环境,它更像进程的概念,不同的进程间通过 IPC 机制进行跨进程通信(如 Socekt),在 Dart中 port 也就是类似 Socket的东西。Isolate拥有自己的事件循环机制,而在每个事件中你还可以去执行更多其他细小任务。

二、 Isolate 能解决什么问题?

做过原生移动端开发的童鞋应该知道,所有的耗时操作都不能在主线程中进行执行(UI 线程),必须开辟一个子线程来处理耗时类工作,否则将导致整个应用程序卡顿或挂掉。而在 Dart中并没有线程这么一说,而是提出了 Isolate 这么一个全新的概念来处理耗时操作。在Dart 基础和 前面Flutter 章节中都有介绍到 async 关键字来进行异步处理,而所谓的异步其实也是运行在同一线程中,异步只是说我可以先执行其他的代码片段,等这边有结果后再返回,这一定的和Isolate 区分开来。

为了能直观的看效果,下面我们写一则案例:

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_learn/page/dialog/LoadingViewDialog.dart';

    class IsolatePage extends StatefulWidget{

      @override
      State createState() {
        return IsolatePageState();
      }
    }

    class IsolatePageState extends State {

      var content = "计算中...";

      @override
      Widget build(BuildContext context) {


        //计算0到 num 数值的总和
        int sum(int num) {
          int count = 0;
          while (num > 0) {
            count = count+num;
            num--;
          }
          return count;
        }

        return Scaffold(

          ///FloatingActionButton
          floatingActionButton: FloatingActionButton(
            elevation: 0,
            child: Text('计算'),
            onPressed: () {

              setState(() {
                content = "总和${sum(100)}";
              });

            },
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,

          appBar: AppBar(
            title: Text("Isolate"),
          ),

          body: SafeArea(
              child:Center(
                child: Column(
                  children: [
                    Container(
                      width: double.infinity,
                      height: 400,
                      //前面章节中的自定义View
                      child: LoadingViewDialog(
                        dialogWidth: double.infinity,
                          //调用对话框
                        progress: CircularProgressIndicator(
                          strokeWidth: 3,
                          //背景颜色
                          backgroundColor: Colors.red,
                        ),

                        content: Text(
                          content,
                          style: TextStyle(color: Colors.blue),
                        ),
                        maxShowTime: 10000,
                      )
                    ),
                  ],
                ),
              ),
          ),
        );
      }
    }

这段代码并没什么难度,就是在点击 FloatingActionButton 后触发 sum方法进行累加计算模拟一个耗时操作,在界面上使用前面章节中的 自定义 LoadingViewDialog 组件来实现一个等待效果。

模拟1.gif

可以看到点击计算按钮后很快就计算出了结果值,当然这是绝对理想的状态下。因为我们的计算并不复杂即使在主线程中计算也并未感知耗时的过程。那么现在将100 调整到 10000000000后来看下表现呢?

模拟2.gif

在修改计算值后再次点击计算按钮,整个应用是直接卡死掉了。在实际开发你的耗时操作可能不是这么一个计算,可能更多的是处理网络上的一些请求或处理结果等,然而类似的这样问题应该是放到隔离器中去处理。

三、怎么创建 Isolate?

还是先回到源码中去,在源码中有这么一个派生函数:

      /**
       * Creates and spawns an isolate that shares the same code as the current
       * isolate.
       *
       * The argument [entryPoint] specifies the initial function to call
       * in the spawned isolate.
       * The entry-point function is invoked in the new isolate with [message]
       * as the only argument.
       *
       * The function must be a top-level function or a static method
       * that can be called with a single argument,
       * that is, a compile-time constant function value
       * which accepts at least one positional parameter
       * and has at most one required positional parameter.
       * The function may accept any number of optional parameters,
       * as long as it *can* be called with just a single argument.
       * The function must not be the value of a function expression
       * or an instance method tear-off.
       *
       * Usually the initial [message] contains a [SendPort] so
       * that the spawner and spawnee can communicate with each other.
       *
       * If the [paused] parameter is set to `true`,
       * the isolate will start up in a paused state,
       * just before calling the [entryPoint] function with the [message],
       * as if by an initial call of `isolate.pause(isolate.pauseCapability)`.
       * To resume the isolate, call `isolate.resume(isolate.pauseCapability)`.
       *
       * If the [errorsAreFatal], [onExit] and/or [onError] parameters are provided,
       * the isolate will act as if, respectively, [setErrorsFatal],
       * [addOnExitListener] and [addErrorListener] were called with the
       * corresponding parameter and was processed before the isolate starts
       * running.
       *
       * If [debugName] is provided, the spawned [Isolate] will be identifiable by
       * this name in debuggers and logging.
       *
       * If [errorsAreFatal] is omitted, the platform may choose a default behavior
       * or inherit the current isolate's behavior.
       *
       * You can also call the [setErrorsFatal], [addOnExitListener] and
       * [addErrorListener] methods on the returned isolate, but unless the
       * isolate was started as [paused], it may already have terminated
       * before those methods can complete.
       *
       * Returns a future which will complete with an [Isolate] instance if the
       * spawning succeeded. It will complete with an error otherwise.
       */
      external static Future spawn(
          void entryPoint(T message), T message,
          {bool paused: false,
          bool errorsAreFatal,
          SendPort onExit,
          SendPort onError,
          @Since("2.3") String debugName});

参数作用:

①、entryPoint(必传): 传入的是一个函数,该函数指定要在派生的 isolate中调用的初始函数。这个函数必须是可以以单一参数调用的全局函数或静态方法,否则报错。

②、message(必传): 携带 SendPort的初始化消息,以便不同的 隔离区之间通信。

③、paused (非必传):如果指定pause为 true,则会在 message 调用 entryPoint函数之前将 isolate以暂停状态启动。如果需要恢复隔离器的状态使用
isolation.resume(isolation. pausecapability)即可。

④、errorsAreFatal(非必传):如果指定该参数为 true。则在 isolate中的 addOnExitListener 和 addErrorListener 监听事件中得到响应的退出和异常信息。

⑤、onExit、onError(配合 参数 ④一起使用)。

⑥、debugName(非必传),如果设置了该参数,在控制台相应的日字信息会通过该标识显示。

再次说明:其中 entryPoint 参数一定是一个静态函数或者全局函数,message 参数则是由 ReceivePort 创建的 SendPort,通过 receivePort.sendPort方式可以拿到。
下面来用get到的新技能将上面会卡死程序的计算改造下:
    import 'dart:isolate';

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_learn/page/dialog/LoadingViewDialog.dart';

    class IsolatePage extends StatefulWidget{

      @override
      State createState() {
        return IsolatePageState();
      }
    }


    class IsolatePageState extends State {

      var content = "计算中...";


      static Future calculation(int n) async{

        //首先创建一个ReceivePort,因为创建isolate所需的参数,必须要有SendPort,SendPort需要ReceivePort来创建
        final response = new ReceivePort();
        //开始创建isolate,createIsolate是创建isolate必须要的参数。
        Isolate isolate = await Isolate.spawn(createIsolate,response.sendPort);

        //获取sendPort来发送数据
        final sendPort = await response.first as SendPort;
        //接收消息的ReceivePort
        final answer = new ReceivePort();
        //发送数据
        sendPort.send([n,answer.sendPort]);
        //获得数据并返回
        return answer.first;
      }

       //创建isolate必须要的参数
      static void createIsolate(SendPort initialReplyTo){
        final port = new ReceivePort();
        //绑定
        initialReplyTo.send(port.sendPort);
        //监听
        port.listen((message){
          //获取数据并解析
          final data = message[0] as num;
          final send = message[1] as SendPort;
          //返回结果
          send.send(sum(data));
        });

      }

      //计算0到 num 数值的总和
      static num sum(int num) {
        int count = 0;
        while (num > 0) {
          count = count+num;
          num--;
        }
        return count;
      }

      @override
      Widget build(BuildContext context) {


        return Scaffold(

          ///FloatingActionButton
          floatingActionButton: FloatingActionButton(
            elevation: 0,
            child: Text('计算'),
            onPressed: () {
              calculation(100000000).then((onValue){
                setState(() {
                  content = "总和$onValue";
                  print("计算结果:$onValue");
                });
              });
            },
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,

          appBar: AppBar(
            title: Text("Isolate"),
          ),

          body: SafeArea(
              child:Center(
                child: Column(
                  children: [
                    Container(
                      width: double.infinity,
                      height: 400,
                      //前面章节中的自定义View
                      child: LoadingViewDialog(
                        dialogWidth: double.infinity,
                          //调用对话框
                        progress: CircularProgressIndicator(
                          strokeWidth: 3,
                          //背景颜色
                          backgroundColor: Colors.red,
                        ),

                        content: Text(
                          content,
                          style: TextStyle(color: Colors.blue),
                        ),
                        maxShowTime: 10000,
                      )
                    ),
                  ],
                ),
              ),
          ),
        );
      }
    }

运行效果:

Isolate模拟3.gif

可以看到再次点击计算按钮后,从加载圈圈就可以看出。整个程序照常运行,依然纵享湿滑。完美解决了上面的卡死现象。

四、Isolate的暂停,恢复,以及关闭

    //恢复 isolate 的使用
    isolate.resume(isolate.pauseCapability);

    //暂停 isolate 的使用
    isolate.pause(isolate.pauseCapability);

    //结束 isolate 的使用
    isolate.kill(priority: Isolate.immediate);

    //赋值为空 便于内存及时回收
    isolate = null;

你可能会问有没有更简单的方式啊? Isolate写起来好麻烦。确实是有的,由于Dart 中的Isolate确实比较重量级,因此Flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作,但其内部仍然还是 Isolate 在工作,只是 Flutter 在框架层帮我们做了简易优化。

五、compute 的使用

尽管compute在使用上很简单,也能处理耗时操作。但它的使用还是有很多限制的,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。这往往根据实际开发中业务情况来取决,如果只是单纯的处理几次耗时工作可以使用 compute,如果是需要一个任务来长时间的处理耗时类工作 则需要使用 Dart 中的 Isolate。

接下来使用 compute 简化上面的代码,修改 FloatingActionButton按钮 点击后使用 compute 方式来处理:

  ///FloatingActionButton
  floatingActionButton: FloatingActionButton(
    elevation: 0,
    child: Text('计算'),
    onPressed: () {

      compute(sum,100000000).then((onValue){
        setState(() {
          content = "总和$onValue";
          print("计算结果:$onValue");
        });
      });
    },
  ),

效果如下:

compute模拟4.gif

效果和直接使用 Isolate 处理一样,纵享湿滑 哈哈O(∩_∩)O

温馨提示: compute 所传入的函数和 Isolate 一样 必须是可以以单一参数调用的全局函数或静态方法,否则会报错。

本章节对 Isolate 的基本招式就打完了,更多高深秘法需要深入内部自行修炼了。

好了本章节到此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星;你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O

实例源码地址:https://github.com/zhengzaihong/flutter_learn/blob/master/lib/page/isolate/IsolatePage.dart

你可能感兴趣的:(Flutter第十七章(Isolate 并发))