flutter或dart中如何简单高效创建一个子线程?

462 阅读3分钟

1 为什么不用Dart内置的方式创建子线程?

Dart内置的子线程方式来创建子线程会面临一个主线程和子线程之间的数据通讯不够优雅,而随着数据通讯的多个端的介入,并要 进行通讯数据的区分时,那代码会变的很复杂。可读性不高,难以维护。 比如下方声明一个isoate然后需要在里面处理3个不同的任务, 而通讯的数据也分别来自3个客户端, 那么代码如下:

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

import 'package:test/test.dart';

// 声明任务类型
enum Task { task1, task2, task3 }

/// 客户端类型
enum Client { client1, client2, client3 }

/// 通讯数据结构
class Message {
  Task task;
  Client client;
  String data;
  Message({required this.task, required this.client, required this.data});
}

void main() {
  group('A group of tests', () {
    test('tmp', () async {
      ReceivePort receivePort = ReceivePort();
      await Isolate.spawn<SendPort>((SendPort isolateSendPort) async {
        ReceivePort isolateReceivePort = ReceivePort();
        isolateSendPort.send(isolateReceivePort.sendPort);
        await for (Message message in isolateReceivePort) {
          switch (message.task) {
            case Task.task1:
              message.data = "Data from task1";
              isolateSendPort.send(message);
              break;
            case Task.task2:
              message.data = "Data from task2";
              isolateSendPort.send(message);
              break;
            case Task.task3:
              message.data = "Data from task3";
              isolateSendPort.send(message);
              break;
          }
        }
      }, receivePort.sendPort);

      late SendPort sendPort;
      Completer<void> isGetSendPortFuture = Completer();
      receivePort.listen((message) {
        if (message is SendPort) {
          sendPort = message;
          isGetSendPortFuture.complete();
        } else if (message is Message) {
          print("client: ${message.client}, task: ${message.task}, data: ${message.data}");
          switch (message.client) {
            case Client.client1:
              print("client1: receive: ${message.data}");
              break;
            case Client.client2:
              print("client2: receive: ${message.data}");
              break;
            case Client.client3:
              print("client3: receive: ${message.data}");
              break;
          }
        }
      });
      await isGetSendPortFuture.future;
      sendPort.send(Message(client: Client.client1, task: Task.task1, data: "data from client1"));
      sendPort.send(Message(client: Client.client2, task: Task.task2, data: "data from client2"));
      sendPort.send(Message(client: Client.client3, task: Task.task3, data: "data from client3"));
      await Future.delayed(Duration(seconds: 10));
    });
  });
}

打印输出结果

client: Client.client1, task: Task.task1, data: Data from task1
client1: receive: Data from task1
client: Client.client2, task: Task.task2, data: Data from task2
client2: receive: Data from task2
client: Client.client3, task: Task.task3, data: Data from task3
client3: receive: Data from task3

这样的代码看起来是很笨重的,在后期维护时,需要付出更多的心理负担。所以有必要想出一种简单的方式来解决线程之间的数据通讯, 从而解决代码复杂性问题。

2 引入管道概念来解决线程通信的问题。

如果我们把线程的通信看成是一个channel 而线程需要做的就是从channel中监听数据并返回数据,那么引入这样的概念, 将有助余代码的简化.

3 使用channel方式来处理线程的通信。

3.1 安装wuchuheng_isolate_channel

通过dart 方式安装

$ dart pub add wuchuheng_isolate_channel 

通过flutter 方式安装

$ flutter pub add wuchuheng_isolate_channel 

3.2 使用channel概念实现线程间的通信

import 'package:test/test.dart';
import 'package:wuchuheng_isolate_channel/wuchuheng_isolate_channel.dart';

T enumFromString<T>(List<T> values, String value) {
  return values.firstWhere((v) => v.toString().split('.')[1] == value);
}

/// 声明channel
enum ChannelName { channel1, channel2, channel3 }

void main() {
  group('A group of tests', () {
    test('tmp', () async {
      final Task task = await IsolateTask((dynamic message, channel) async {
        final ChannelName channelName = enumFromString<ChannelName>(ChannelName.values, channel.name);
        print(message);
        switch (channelName) {
          case ChannelName.channel1:
            channel.send('Data from channel1');
            break;
          case ChannelName.channel2:
            channel.send('Data from channel2');
            break;
          case ChannelName.channel3:
            channel.send('Data from channel3');
            break;
        }
      });
      final channel1 = task.createChannel(name: ChannelName.channel1.name)

        /// channel 1监听消息
        ..listen((dynamic message, channel) async {
          print(message);
        });
      final channel2 = task.createChannel(name: ChannelName.channel2.name)

        /// channel 2监听消息
        ..listen((dynamic message, channel) async {
          print(message);
        });
      final channel3 = task.createChannel(name: ChannelName.channel3.name)

        /// channel 3监听消息
        ..listen((dynamic message, channel) async {
          print(message);
        });
      channel1.send("Send data to channel1.");
      channel2.send("Send data to channel2.");
      channel3.send("Send data to channel3.");
      await Future.delayed(Duration(seconds: 1));
    });
  });
}

打印输出结果

Send data to channel1.
Send data to channel2.
Send data to channel3.
Data from channel1
Data from channel2
Data from channel3

4 总结

当引入channel的概念去写代码时, 数据的交互是以channel的方式进行的,而不用去额外定义数据结构去区分数据的来源和意图。通过channel的概念去监听数据,然后 处理数据,最后再从原来的channel返回就可以了。从而简化的线程之间的数据交互的复杂性问题。

5 参考资料