flutter中的'多线程/多任务'简单介绍

919 阅读4分钟

flutter/dart中没有多线程,只有单线程。WTF?其实前面说的多线程在它里面只是换了个名字:隔离(isolate / ˈaɪsəleɪt /)。

主线程(UI线程)

flutter默认是在单线程(主线程)运行,它里面包含2个队列:

  • 微任务队列Microtask Queue
  • 事件/任务/宏队列(Event Queue)

所以加上默认的主线程Main Queue是3个队列。

执行顺序

主线程所在的代码会同步执行Main Queue,然后再执行它里面的微任务Microtask Queue与事件/任务队列Event Queue。 比如:

void main() {
  // 进入事件队列。如果有多个加入事件队列,按加入事件队列的顺序先后执行。
  Future((){
    print('Future');
  });
  // 进入主线程。代码从上到下依次执行
  print('main');
  // 进入微任务队列。如果有多个加入微任务队列,按加入微任务队列的顺序先后执行。
  scheduleMicrotask((){
    print('scheduleMicrotask');
  });
}

执行后输出:

flutter: main
flutter: scheduleMicrotask
flutter: Future

如何另外启动一个线程呢?

在flutter/dart里,另外一个线程又叫做隔离(isolate),它是真的内存隔离,所以互相之间交互需要引入一个信使(port)。根据用途,我们可以大概的分为三种:

  1. 手动创建Isolate(完整控制)
  • 只能传递可序列化的数据
  • 不能传递函数闭包
  • 不能直接共享内存
Future<void> manualIsolate() async {
  // 创建接收消息的端口
  final receivePort = ReceivePort();

  // 启动新的isolate
  final isolate = await Isolate.spawn(
    workerFunction,       // isolate中要执行的函数
    receivePort.sendPort, // 传递给worker的发送端口
  );

  // 监听接收到的消息
  receivePort.listen((message) {
    if (message is String) {
      print('收到消息: $message');
    } else if (message == 'done') {
      // 清理工作
      receivePort.close();
      isolate.kill();
    }
  });
}

// 注意:worker函数必须是顶级函数或静态方法
void workerFunction(SendPort sendPort) {
  // 在这里执行耗时操作
  for (var i = 0; i < 5; i++) {
    sendPort.send('处理进度: $i');
  }
  sendPort.send('done');
}
  1. compute 方式(最简单)
  • 实际上是对Isolate.spawn的封装
  • 自动处理了消息传递和清理
  • 适合一次性的计算任务
// compute 实际上是对 Isolate 的封装,适合一次性的计算任务
Future<List<String>> processData(List<String> data) async {
  final result = await compute(
    // 第一个参数:要在新isolate中执行的顶级函数或静态方法
    (List<String> input) {
      // 这里的代码在新的isolate中执行
      return input.map((e) => e.toUpperCase()).toList();
    },
    // 第二个参数:传递给函数的参数
    data,
  );
  return result;
}
  1. 双向通信的Isolate(复杂场景)
class IsolateManager {
  late Isolate isolate;
  late ReceivePort receivePort;
  late SendPort workerSendPort;
  
  Future<void> start() async {
    receivePort = ReceivePort();
    
    // 启动worker isolate
    isolate = await Isolate.spawn(
      workerFunction,
      receivePort.sendPort,
    );

    // 等待worker发送它的SendPort
    workerSendPort = await receivePort.first;
    
    // 设置消息处理
    receivePort.listen((message) {
      if (message is SendPort) return; // 忽略初始的SendPort消息
      print('收到worker消息: $message');
    });
  }

  void sendMessage(String message) {
    workerSendPort.send(message);
  }

  void dispose() {
    receivePort.close();
    isolate.kill();
  }
}

void workerFunction(SendPort mainSendPort) {
  // worker的接收端口
  final receivePort = ReceivePort();
  
  // 首先发送worker的SendPort给main isolate
  mainSendPort.send(receivePort.sendPort);
  
  // 处理接收到的消息
  receivePort.listen((message) {
    print('Worker收到消息: $message');
    // 可以处理后发送结果回去
    mainSendPort.send('处理结果: $message');
  });
}

// 使用示例
void example() async {
  final manager = IsolateManager();
  await manager.start();
  
  // 发送消息给worker
  manager.sendMessage('Hello Worker');
  
  // 完成后清理
  await Future.delayed(Duration(seconds: 5));
  manager.dispose();
}

另外再额外补充可能一个用到的场景:

  1. 持久化的后台Worker(长期运行的任务)
class BackgroundWorker {
  late Isolate isolate;
  late ReceivePort receivePort;
  late SendPort workerSendPort;
  final _taskQueue = Queue<Task>();
  bool _isProcessing = false;

  Future<void> initialize() async {
    receivePort = ReceivePort();
    isolate = await Isolate.spawn(
      workerFunction,
      receivePort.sendPort,
    );

    workerSendPort = await receivePort.first;
    _setupMessageHandler();
  }

  void _setupMessageHandler() {
    receivePort.listen((message) {
      if (message is SendPort) return;
      
      if (message == 'ready') {
        _processNextTask();
      } else {
        // 处理任务结果
        print('任务结果: $message');
        _isProcessing = false;
        _processNextTask();
      }
    });
  }

  void addTask(Task task) {
    _taskQueue.add(task);
    if (!_isProcessing) {
      _processNextTask();
    }
  }

  void _processNextTask() {
    if (_taskQueue.isEmpty) return;
    
    _isProcessing = true;
    final task = _taskQueue.removeFirst();
    workerSendPort.send(task);
  }
}

class Task {
  final String type;
  final Map<String, dynamic> data;
  
  Task(this.type, this.data);
}

// Worker实现
void workerFunction(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  mainSendPort.send(receivePort.sendPort);

  receivePort.listen((message) async {
    if (message is Task) {
      // 处理任务
      final result = await _processTask(message);
      mainSendPort.send(result);
    }
    // 表示准备接收下一个任务
    mainSendPort.send('ready');
  });
}

// 使用示例
void main() async {
  final worker = BackgroundWorker();
  await worker.initialize();

  worker.addTask(Task('process', {'data': 'some data'}));
  worker.addTask(Task('convert', {'file': 'path/to/file'}));
}

那这几种我们什么场景下又该选用谁?没有选择困难症:

  • 短任务用compute
  • 需要状态管理用手动Isolate
  • 需要双向通信用ReceivePort/SendPort
  • 长期运行任务用持久化Worker

如何传递复杂对象?

前面说只能传递可序列化的数据?复杂对象自己序列化好就可以了。这里用compute方式举例

// ❌ 错误示例:不能传递无法序列化的对象
Future<void> wrongUsage() async {
  final controller = StreamController();
  
  await compute(
    (StreamController ctrl) {
      // 错误!StreamController无法序列化
      ctrl.add('data');
    },
    controller, // 这会抛出错误
  );
}

// ❌ 错误示例:不能使用闭包
String globalVar = 'test';
Future<void> wrongClosure() async {
  final localVar = 'local';
  
  await compute(
    () {
      // 错误!无法访问外部变量
      return globalVar + localVar;
    },
    null,
  );
}

// ✅ 正确示例:传递所需的所有数据
Future<String> correctUsage() async {
  final data = {
    'global': globalVar,
    'local': 'local',
  };
  
  return compute(
    (Map<String, String> input) {
      return input['global']! + input['local']!;
    },
    data,
  );
}

// ✅ 一个处理复杂对象的更完整的示例
class ComplexData {
  final String name;
  final int value;
  
  ComplexData(this.name, this.value);
  
  // 注意:需要能被序列化
  Map<String, dynamic> toMap() => {
    'name': name,
    'value': value,
  };
  
  factory ComplexData.fromMap(Map<String, dynamic> map) => ComplexData(
    map['name'],
    map['value'],
  );
}

Future<ComplexData> processComplexData(ComplexData data) async {
  // 传递复杂对象时需要序列化
  final result = await compute(
    (Map<String, dynamic> input) {
      final data = ComplexData.fromMap(input);
      // 处理数据
      return ComplexData(
        data.name.toUpperCase(),
        data.value * 2,
      ).toMap();
    },
    data.toMap(),
  );
  
  return ComplexData.fromMap(result);
}

优化性能的可选方式推荐

如果数据量太大,也可以分片处理。

// 批处理优化
Future<List<String>> optimizedBatchProcess(List<String> items) async {
  // 将大量数据分块处理
  final chunks = _splitIntoChunks(items, 1000);
  
  // 并行处理多个块
  final futures = chunks.map((chunk) => 
    compute(
      (List<String> input) => processChunk(input),
      chunk,
    )
  );
  
  // 等待所有处理完成并合并结果
  final results = await Future.wait(futures);
  return results.expand((x) => x).toList();
}

List<List<T>> _splitIntoChunks<T>(List<T> list, int chunkSize) {
  final result = <List<T>>[];
  for (var i = 0; i < list.length; i += chunkSize) {
    result.add(list.sublist(
      i,
      i + chunkSize > list.length ? list.length : i + chunkSize,
    ));
  }
  return result;
}

// 缓存计算结果
class ComputeCache {
  static final _cache = <String, dynamic>{};
  
  static Future<T> computeWithCache<T>(
    String key,
    Future<T> Function() computation,
  ) async {
    if (_cache.containsKey(key)) {
      return _cache[key] as T;
    }
    
    final result = await computation();
    _cache[key] = result;
    return result;
  }
}