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
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
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.
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
with flutter
$ flutter pub add wuchuheng_isolate_channel
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));
});
});
}
printout
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
,Implementation library that handles threaded data interactions with the concept ofchannel
.
Top comments (0)