Flutter学习-Isolate

1,497 阅读4分钟

Flutter的多线程

Flutter中使用的是Dart语言,而Dart是一种单线程的设计。虽然如此,但不代表Flutter(或者说Dart)就不支持多线程。这篇文章就研究一下如何在Flutter(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.
 ......
 *
 * Isolates run code in its own event loop, and each event may run smaller tasks
 * in a nested microtask queue.
 *
 ...

简单摘要一下大概意思:

  • dart代码执行是在isolate中,在相同的isolate中,代码可以访问定义的类和值。不同的isolate之间,使用port方式进行通讯。
  • 当前的isolate与创建出来的isolate通常是不同的。
  • ......
  • Isolates中运行的代码是在它自己的事件循环中,并且每一个事件可以嵌套运行在微任务队列中。
  • ......

1.实现多线程逻辑

通过以上的这些注释,说明使用isolate可以实现一些多线程的相关操作。

示例代码:

void main() {
  isolateDemo();
}

void isolateDemo() {
  Future(() => Isolate.spawn(func1, 123)).then((value) => print('1结束了'));
  Future(() => Isolate.spawn(func1, 123)).then((value) => print('2结束了'));
  Future(() => Isolate.spawn(func1, 123)).then((value) => print('3结束了'));
  Future(() => Isolate.spawn(func1, 123)).then((value) => print('4结束了'));
  Future(() => Isolate.spawn(func1, 123)).then((value) => print('5结束了'));
}

void func1(int count) {}

打印结果:

每次运行的结果都是不一样的,所以完全可以用isolate来实现多线程的相关逻辑。

2. isolate不等价于Thread

在ios开发中,我们使用多线程都是基于NSThread来实现,此时大家应该会感觉到isolate和thread差不多,其实不然。isolate要比thread要更底层一些,更像是个进程。

在声明注释中有这样的描述:

  • Different isolates can communicate by sending values through ports (see [ReceivePort], [SendPort]).
  • Isolates run code in its own event loop, and each event may run smaller tasks in a nested microtask。

意思是说不同的isolate之间用port通讯,isolate有自己的事件队列和微任务队列。这两点就可以证明‘isolate是进程操作’的猜想。

3. isolate的独立空间

int a = 10;

void main() {
  Isolate.spawn(func, 123);
  //sleep 1s
  sleep(Duration(seconds: 1));
  print(Isolate.current.debugName + ' a = $a');
}

void func(int count) {
  a = count;
  print(Isolate.current.debugName + ' func中a = $a');
}
  • 定义一个全局变量a=10
  • main函数中,开启一个isolate,调用func函数,传参入参数‘123’。睡一秒后,打印a的值
  • func函数中,接受到的形参赋值给a,打印a的值

运行结果:

flutter: func func中a = 123
flutter: main a = 10

无论sleep设置多长时间,两个打印的a值都不相同,这里可以证明a在两个isolate中,有各自的独立内存空间,互不影响。还有一点好处是不用担心多线程的资源抢夺问题。

4. isolate之间的数据交互

通过端口来达到isolate之间的数据交互。

int a = 10;
void main() {
  test();
}

void test() async {
  //创建port端口
  ReceivePort port = ReceivePort();
  //创建isolate实例,注意此处需要await修饰,但此await不会影响后面的同步代码执行,
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  port.listen((message) {
    a = message;
    print('listen a = $a');
    //关闭端口
    port.close();
    //关闭iso
    iso.kill();
  });

  sleep(Duration(seconds: 1));
  print('a = $a');
}

void func(SendPort send) {
  send.send(1000);
}

代码中创建isolate实例,语法要求需要await修饰,但此时的await和修饰Future的await的功能不一样,它不会阻碍同步代码的执行。

对修饰Future的await修饰情况不了解的,可以查看本人之前的文章Flutter学习-Dart异步编程中的async和await中的示例。

注意:sleep函数无论设置多长时间,都是先打印,后执行port.listen中的回调。

打印结果:

flutter: a = 10
flutter: listen a = 1000

5. compute

因为isolate比较偏底层,所以,代码写起来比较繁琐。 compute是对isolate封装,使用起来更方便一些。比如compute中的回调方法是有返回值的,而isolate.spawn中的回调方法是没有的。

int a = 10;
void main() {
  computeDemo();
}

void computeDemo() async {
  print('外部代码1');

  a = await compute(func2, a);
  print('a = $a');

  sleep(Duration(seconds: 1));
  print('外部代码2');
}

//有返回值
int func2(int count) {
  return 10000;
}

打印结果:

flutter: 外部代码1
flutter: a = 10000
flutter: 外部代码2

通过compute声明中的注释,我们能看到compute中的回调方法的返回值是Future类型,那么我们也可以用链式的方式来写代码,这样就不会阻塞同步代码了:

int a = 10;
void main() {
  computeDemo();
}

void computeDemo() {
  print('外部代码1');

  //链式实现
  compute(func2, a).then((value) {
    a = value;
    print('a = $a');
  });

  sleep(Duration(seconds: 1));
  print('外部代码2');
}

int func2(int count) {
  return 10000;
}

打印结果:

flutter: 外部代码1
flutter: 外部代码2
flutter: a = 10000