美文网首页
flutter中如何创建并且使用一个常驻isolate

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

作者: 晨曦中的花豹 | 来源:发表于2022-03-25 16:28 被阅读0次

上一篇文章说到了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}

相关文章

网友评论

      本文标题:flutter中如何创建并且使用一个常驻isolate

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