DEV Community

Cover image for How to create a sub-thread easily and efficiently in flutter or dart ?
wuchuheng
wuchuheng

Posted on

How to create a sub-thread easily and efficiently in flutter or dart ?

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

Creating sub-threads with Darts 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

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));
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

Printout

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
Enter fullscreen mode Exit fullscreen mode

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

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

$ dart pub add wuchuheng_isolate_channel 
Enter fullscreen mode Exit fullscreen mode

with flutter

$ flutter pub add wuchuheng_isolate_channel 
Enter fullscreen mode Exit fullscreen mode

3.2 Implementing inter-thread communication using the channel concept

sample code

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));
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

printout

Send data to channel1.
Send data to channel2.
Send data to channel3.
Data from channel1
Data from channel2
Data from channel3
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)