How to create a sub-thread easily and efficiently in flutter or dart ?
Tags: flutter, dart, isolate
Published: 2022-11-14 09:16:42

## 1 Why not use the `Dart` built-in way to create sub-threads?

Creating sub-threads with `Dart`s built-in sub-threading approach will face a lack of elegance in data communication between the main thread and the sub-threads, and with the intervention of multiple ends of data communication and the need to
The code becomes complex when multiple ends of data communication are involved and the communication data is differentiated. It is not very readable and difficult to maintain. For example, if you declare an `isoate` below and then need to handle 3 different types of tasks in it.
And the communication data comes from each of the three clients, then the code is as follows: 

> Sample Code


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

import 'package:test/test.dart';

// decleare task type
enum Task { task1, task2, task3 }

/// decleare client type
enum Client { client1, client2, client3 }

/// Communication data structure
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));
    });
  });
}
```

> Printout

``` bash
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
```

Such code looks unwieldy and requires more mental effort when maintaining it later. So it is necessary to come up with a simple way to solve the code complexity problem by solving the data communication between threads.

## 2 Introduce the concept of pipes to solve the problem of thread communication.

If we think of thread communication as a channel and all a thread needs to do is listen for data from the `channel` and return it, then introducing the concept of
will help simplify the rest of the code.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xbwz7jxybxuo089oplb.jpg)

## 3 Use the `channel` method to handle thread communication.

### 3.1 Installing `wuchuheng_isolate_channel`

`wuchuheng_isolate_channel` is a library that resolves `isolate` data interactions as a `channel`.
with `dart` 
``` bash
$ dart pub add wuchuheng_isolate_channel 
```
with `flutter`
``` bash
$ flutter pub add wuchuheng_isolate_channel 
```

### 3.2 Implementing inter-thread communication using the `channel` concept

> sample code

```  dart
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);
}

/// declare 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)

        /// listening messages by channel 1
        ..listen((dynamic message, channel) async {
          print(message);
        });
      final channel2 = task.createChannel(name: ChannelName.channel2.name)

        /// listening messages by channel 1
        ..listen((dynamic message, channel) async {
          print(message);
        });
      final channel3 = task.createChannel(name: ChannelName.channel3.name)

        /// listening messages by 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));
    });
  });
}
```

> printout

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

## 4 Summary
When the concept of `channel` is introduced to the code, the data interaction is done in a `channel` manner, without the need to define additional data structures to distinguish the source and intent of the data. The concept of `channel` is used to listen to data, then
processing the data, and then returning it from the original `channel`. This simplifies the complexity of data interaction between threads.

## 5 References

* [`wuchuheng_isolate_channel`](https://pub.dev/packages/wuchuheng_isolate_channel) ,Implementation library that handles threaded data interactions with the concept of `channel`.