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<StatefulWidget> createState() {
        return IsolatePageState();
      }
    }

    class IsolatePageState extends State<IsolatePage> {

      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: <Widget>[
                    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<Isolate> spawn<T>(
          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<StatefulWidget> createState() {
        return IsolatePageState();
      }
    }


    class IsolatePageState extends State<IsolatePage> {

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


      static Future<dynamic> 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: <Widget>[
                    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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容