深入瞭解Flutter的isolate(4) --- 使用Compute寫isolates

小德_koude發表於2019-01-12

0x00 前言

前面講了如何建立isolate,這篇文章講建立isolate的另一種方法。

0x01 使用isolates的方法

使用isolates的方法種:

  1. 高階API:Compute函式 (用起來方便)
  2. 低階API:ReceivePort

0x02 Compute函式

Compute函式對isolate的建立和底層的訊息傳遞進行了封裝,使得我們不必關係底層的實現,只需要關注功能實現。

首先我們需要:

  1. 一個函式:必須是頂級函式或靜態函式
  2. 一個引數:這個引數是上面的函式定義入參(函式沒有引數的話就沒有)

比如,還是計算斐波那契數列:

void main() async{
  //呼叫compute函式,compute函式的引數就是想要在isolate裡執行的函式,和這個函式需要的引數
  print( await compute(syncFibonacci, 20));
  runApp(MyApp());
}

int syncFibonacci(int n){
  return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
}
複製程式碼

執行後的結果如下:

flutter: 6765
複製程式碼

是不是很簡單,接下來看下compute函式的原始碼,這裡的程式碼有點複雜,會把分析的新增到程式碼的註釋裡,首先介紹一個compute函式裡用到的函式別名:

ComputeCallback<Q, R>定義如下:

// Q R是泛型,ComputeCallback是一個有引數Q,返回值為R的函式
typedef ComputeCallback<Q, R> = R Function(Q message);
複製程式碼

正式看原始碼:

//compute函式 必選引數兩個,已經講過了
Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message, { String debugLabel }) async {
  //如果是在profile模式下,debugLabel為空的話,就取callback.toString()
  profile(() { debugLabel ??= callback.toString(); });
  final Flow flow = Flow.begin();
  Timeline.startSync('$debugLabel: start', flow: flow);
  final ReceivePort resultPort = ReceivePort();
  Timeline.finishSync();
  //建立isolate,這個和前面講的建立isolate的方法一致
  //還有一個,這裡傳過去的引數是用_IsolateConfiguration封裝的類
  final Isolate isolate = await Isolate.spawn<_IsolateConfiguration<Q, R>>(
    _spawn,
    _IsolateConfiguration<Q, R>(
      callback,
      message,
      resultPort.sendPort,
      debugLabel,
      flow.id,
    ),
    errorsAreFatal: true,
    onExit: resultPort.sendPort,
  );
  final R result = await resultPort.first;
  Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
  resultPort.close();
  isolate.kill();
  Timeline.finishSync();
  return result;
}

@immutable
class _IsolateConfiguration<Q, R> {
  const _IsolateConfiguration(
    this.callback,
    this.message,
    this.resultPort,
    this.debugLabel,
    this.flowId,
  );
  final ComputeCallback<Q, R> callback;
  final Q message;
  final SendPort resultPort;
  final String debugLabel;
  final int flowId;

  R apply() => callback(message);
}

void _spawn<Q, R>(_IsolateConfiguration<Q, R> configuration) {
  R result;
  Timeline.timeSync(
    '${configuration.debugLabel}',
    () {
      result = configuration.apply();
    },
    flow: Flow.step(configuration.flowId),
  );
  Timeline.timeSync(
    '${configuration.debugLabel}: returning result',
    () { configuration.resultPort.send(result); },
    flow: Flow.step(configuration.flowId),
  );
}

複製程式碼

0x03 ReceivePort

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

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

//一個普普通通的Flutter應用的入口
//main函式這裡有async關鍵字,是因為建立的isolate是非同步的
void main() async{
  runApp(MyApp());
  
  //asyncFibonacci函式裡會建立一個isolate,並返回執行結果
  print(await asyncFibonacci(20));
}

//這裡以計算斐波那契數列為例,返回的值是Future,因為是非同步的
Future<dynamic> asyncFibonacci(int n) async{
  //首先建立一個ReceivePort,為什麼要建立這個?
  //因為建立isolate所需的引數,必須要有SendPort,SendPort需要ReceivePort來建立
  final response = new ReceivePort();
  //開始建立isolate,Isolate.spawn函式是isolate.dart裡的程式碼,_isolate是我們自己實現的函式
  //_isolate是建立isolate必須要的引數。
  await Isolate.spawn(_isolate,response.sendPort);
  //獲取sendPort來傳送資料
  final sendPort = await response.first as SendPort;
  //接收訊息的ReceivePort
  final answer = new ReceivePort();
  //傳送資料
  sendPort.send([n,answer.sendPort]);
  //獲得資料並返回
  return answer.first;
}

//建立isolate必須要的引數
void _isolate(SendPort initialReplyTo){
  final port = new ReceivePort();
  //繫結
  initialReplyTo.send(port.sendPort);
  //監聽
  port.listen((message){
    //獲取資料並解析
    final data = message[0] as int;
    final send = message[1] as SendPort;
    //返回結果
    send.send(syncFibonacci(data));
  });
}

int syncFibonacci(int n){
  return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
}
複製程式碼

相關文章