dart 的多线程 Isolate

218 阅读2分钟

Dart 应用通常只会在单线程中处理它们的工作。并且在大多数情况中,这种模式不但简化了代码而且速度也够快.

但是,当你需要进行一个非常复杂的计算时,例如解析一个巨大的 JSON 文档。如果这项工作耗时超过了 16 毫秒,那么你的用户就会感受到掉帧。

为了避免掉帧,像上面那样消耗性能的计算就应该放在后台处理。在 Android 平台上,这意味着你需要在不同的线程中进行调度工作。而在 Flutter 中,你可以使用一个单独的 Isolate

Isolate

dart 代码只能使用同一个 Isolate 中的内容,Isolate 有自己的内存和事件循环机制;不同的 Isolate 是内存隔离的,这样因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

因为Isolate之间没有内存共享,所以Isolate之间的通讯只能通过port进行。且消息的传递是异步的。

Isolate 的创建

Isolate 一般通过spawn / spawnUri来创建对象;

Isolate.spawn()

entryPoint 参数是必传的要在isolate中要调用的初始化函数,message是初始化函数要接收的信息。

external static Future<Isolate> spawn<T>(
      void entryPoint(T message), T message,
      {bool paused = false, // 暂停
      bool errorsAreFatal = true, // 致命性错误
      SendPort? onExit, // 退出时
      SendPort? onError, // 发生错误时
      @Since("2.3") String? debugName});

Isolate.spawnUri()

简单了解源码,spawnUri 需要三个必要参数,uri 为其他 Isolate 代码文件的路径;列表 args 为传递的参数列表,message 动态消息,一般是 SendPort

external static Future<Isolate> spawnUri(
      Uri uri,
      List<String> args,
      var message,
      {bool paused = false,
      SendPort? onExit,
      SendPort? onError,
      bool errorsAreFatal = true,
      bool? checked,
      Map<String, String>? environment,
      @Deprecated('The packages/ dir is not supported in Dart 2')
          Uri? packageRoot,
      Uri? packageConfig,
      bool automaticPackageResolution = false,
      @Since("2.3")
          String? debugName});
          

例子:

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

int i = 0;
IntObject intobj = IntObject();

void main() async{
  // 消息接收端口
  final receive = ReceivePort(); // 
  receive.listen((message) { // 监听端口
    print("main: Receive data : $message, i = $i , intobject = ${intobj.geti()}");
  });

  Isolate isolate = await Isolate.spawn(isolatefun, receive.sendPort); // 创建isolate,异步 
}
// 初始化函数,接收一个SendPort 参数
void isolatefun(SendPort sendport){
  int counter = 0 ;
  // 定时器
  Timer.periodic(Duration(seconds: 3),(_){
    counter ++ ;
    i++;
    intobj.add();

    String setMsg = "Notification data : $counter";
    print("send message : $setMsg , i = $i , intobject = ${intobj.geti()}");
    
    // 痛过端口发送信息    
    sendport.send(setMsg);
  });

}

class IntObject {
  int _i = 0;

  add (){
    _i++;
  }

  int geti(){
    return _i;
  }

}

运行结果:

1683183872328.png

可以看到我们在isolate 中的操作并没有影响到main()函数中关于i 和 intobj 的值。