flutter中如何创建并且使用一个常驻isolate

上一篇文章说到了ffi调用c函数,但是结尾留了一个问题就是c函数如果是一个延时操作,整个流程会调用多次,并且每一个c函数调用都使用了compute进行异步调用(这里不在业务层处理主要是想要业务层显得简洁,其实可以使用compute对整个业务进行异步),这个时候compute频繁创建isolate导致整个流程下来消耗时间过长,然后就有了一个想法,我能不能创建一个常驻的isolate,去执行我的c函数呢
我的思路是

1.熟悉isolate通信

其实最后看下来简单的理解就是两个(A,B)封闭的空间各干各的,中间有两个管道(Port),一个是从A->B,一个是从B->A


WechatIMG23.jpeg

A把要干的事通过管子给到B,B监听到后开始工作,结束之后把结果再通过管道给到A,基本的流程就是这样,撸代码

//特别需要注意:establishConn执行环境是rootIsolate
  static void establishConn() async {
    //第1步: 默认执行环境下是rootIsolate,所以创建的是一个rootIsolateReceivePort
    ReceivePort rootIsolateReceivePort = ReceivePort();
    //第2步: 获取rootIsolateSendPort
    SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
    //第3步: 创建一个newIsolate实例,并把rootIsolateSendPort作为参数传入到newIsolate中,为的是让newIsolate中持有rootIsolateSendPort, 这样在newIsolate中就能向rootIsolate发送消息了
    newIsolate = await Isolate.spawn(createNewIsolateContext,
        rootIsolateSendPort); //注意createNewIsolateContext这个函数执行环境就会变为newIsolate, rootIsolateSendPort就是createNewIsolateContext回调函数的参数
    //第4步: 通过rootIsolateReceivePort接收到来自newIsolate的消息,所以可以注意到这里是await 因为是异步消息
    rootIsolateReceivePort.listen((message) {
      if (message is SendPort) {
        print("newIsolateSendPort $message");
        newIsolateSendPort = message;
      }
    });
  }

  //特别需要注意:createNewIsolateContext执行环境是newIsolate
  static void createNewIsolateContext(SendPort rootIsolateSendPort) async {
    //第1步: 注意callback这个函数执行环境就会变为newIsolate, 所以创建的是一个newIsolateReceivePort
    ReceivePort newIsolateReceivePort = ReceivePort();
    //第2步: 获取newIsolateSendPort, 有人可能疑问这里为啥不是直接让全局newIsolateSendPort赋值,注意这里执行环境不是rootIsolate
    SendPort newIsolateSendPort = newIsolateReceivePort.sendPort;
    //第3步: 特别需要注意这里,这里是利用rootIsolateSendPort向rootIsolate发送消息,只不过发送消息是newIsolate的SendPort, 这样rootIsolate就能拿到newIsolate的SendPort
    rootIsolateSendPort.send(newIsolateSendPort);
    newIsolateReceivePort.listen((message) {
      
    });
  }

这一步结束,我们常驻isolate已经有了,并且它和我们mainIsolate已经建立起了通信通道

2.如何使用这个常驻isolate

首先我想要什么?

我想实现的就是,给你个函数地址,给你参数,你去干活吧,然后结束之后返回给我结果,伪代码是这样的

doWork(Function work,params) async {
    newIsolateSendPort?.send([work,params]);
    ...(work执行(耗时))
    return work执行结果
  }

但是我的回调结果在listen里边啊,我怎么做才能让doWork这个函数等我呢?最后在朋友的提示下,看到了Completer,这个问题就迎刃而解了,这个类可以让你的函数停止,然后调用complete,才会继续执行,这不是正是我想要的嘛?等着,然后listen到结果,执行complete,让doWork函数继续进行?perfect!!!
然后就是一些进一步的优化,

将通信内容(函数地址,参数,id)对象化,
///通知子isolate工作
class SendMessage {
  int id;
  Map<String,dynamic> params;
  Future Function(Map<String,dynamic> params) work;

  SendMessage({
    required this.id,
    required this.params,
    required this.work,
  });
}

///通知main isolate,活干完了,给你结果
class ReceiveMessage {
  int id;
  dynamic result;

  ReceiveMessage({
    required this.id,
    this.result,
  });
}

将常驻isolate表达成单例,最后就是最终的代码

import 'dart:async';
import 'dart:isolate';

class GlobalIsolate {
  static Isolate? _newIsolate;
  static SendPort? _newIsolateSendPort;

  static int _i = 0;

  static final Map<int, dynamic> _works = {};

  static Future isolateDo({
    required Future Function(Map<String,dynamic> params) work,
    required Map<String,dynamic> params,
  }) async {
    if (_newIsolate == null) {
      await _initNewIsolate();
    }
    SendMessage message = SendMessage(id: _i, params: params, work: work);
    _newIsolateSendPort?.send(message);
    Completer c = Completer();
    _works[_i] = c;
    _i++;
    try {
      final result = await c.future;
      return result;
    } catch (e) {
      return null;
    }
  }

  static Future _initNewIsolate() async {
    if (_newIsolate != null) return;
    //第1步: 默认执行环境下是rootIsolate,所以创建的是一个rootIsolateReceivePort
    ReceivePort rootIsolateReceivePort = ReceivePort();
    //第2步: 获取rootIsolateSendPort
    SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
    //第3步: 创建一个newIsolate实例,并把rootIsolateSendPort作为参数传入到newIsolate中,为的是让newIsolate中持有rootIsolateSendPort, 这样在newIsolate中就能向rootIsolate发送消息了
    _newIsolate = await Isolate.spawn(_createNewIsolateContext,
        rootIsolateSendPort); //注意createNewIsolateContext这个函数执行环境就会变为newIsolate, rootIsolateSendPort就是createNewIsolateContext回调函数的参数
    //第7步: 通过rootIsolateReceivePort接收到来自newIsolate的消息,所以可以注意到这里是await 因为是异步消息
    //只不过这个接收到的消息是newIsolateSendPort, 最后赋值给全局newIsolateSendPort,这样rootIsolate就持有newIsolate的SendPort
    // var message = await rootIsolateReceivePort.first;
    //第8步,建立连接成功
    // print(messageList[0] as String);
    // newIsolateSendPort = message as SendPort;
    // print("newIsolateSendPort $message");
    rootIsolateReceivePort.listen((message) {
      if (message is SendPort) {
        print("newIsolateSendPort $message");
        _newIsolateSendPort = message;
      } else if (message is ReceiveMessage) {
        if (_works[message.id] is Completer) {
          Completer c = _works[message.id];
          c.complete(message.result);
          _works.remove(message.id);
        }
      }
    });
  }

//特别需要注意:createNewIsolateContext执行环境是newIsolate
  static void _createNewIsolateContext(SendPort rootIsolateSendPort) async {
    //第4步: 注意callback这个函数执行环境就会变为newIsolate, 所以创建的是一个newIsolateReceivePort
    ReceivePort newIsolateReceivePort = ReceivePort();
    //第5步: 获取newIsolateSendPort, 有人可能疑问这里为啥不是直接让全局newIsolateSendPort赋值,注意这里执行环境不是rootIsolate
    SendPort newIsolateSendPort = newIsolateReceivePort.sendPort;
    //第6步: 特别需要注意这里,这里是利用rootIsolateSendPort向rootIsolate发送消息,只不过发送消息是newIsolate的SendPort, 这样rootIsolate就能拿到newIsolate的SendPort
    rootIsolateSendPort.send(newIsolateSendPort);
    newIsolateReceivePort.listen((message) {
      if (message is SendMessage) {
        message.work(message.params).then((value) => rootIsolateSendPort
            .send(ReceiveMessage(id: message.id, result: value)));
      }
    });
  }
}

///通知子isolate工作
class SendMessage {
  int id;
  Map<String,dynamic> params;
  Future Function(Map<String,dynamic> params) work;

  SendMessage({
    required this.id,
    required this.params,
    required this.work,
  });
}

///通知main isolate,活干完了,给你结果
class ReceiveMessage {
  int id;
  dynamic result;

  ReceiveMessage({
    required this.id,
    this.result,
  });
}

最后测试一下

test() async {
    await GlobalIsolate.init();
    int a = 10;

    final result = await GlobalIsolate.isolateDo(
        params: {"a":a},
        work: (Map<String,dynamic> params) async {
          sleep(Duration(seconds: 2));
          var a= params["a"];
          return {
            "result1":"1 - ${a++}",
            "result2":"2 - ${a++}",
            "result3":"3 - ${a++}",
          };
        }
    );
    print("result = $result");
  }

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

推荐阅读更多精彩内容