Isolates operate on its own threads, where async operations operate on the main thread.
Bidirectional communication
Bidirectional communication between the main thread and isolate1/isolate2 and between isolate2 and isolate1 (with the help of the main thread).
import 'dart:io';
import 'dart:isolate';
main(List<String> args) async {
  ReceivePort isolate1ToMainStream = new ReceivePort();
  ReceivePort isolate2ToMainStream = new ReceivePort();
  SendPort? mainToIsolateStream1;
  isolate1ToMainStream.listen((input) {
    Map data = input as Map;
    if (data['type'] == 'sendPort') {
      mainToIsolateStream1 = data['value'];
      mainToIsolateStream1!.send('Message from Main');
    } else {
      print('Message recieved in Main: ${data['value']}');
    }
  });
  isolate2ToMainStream.listen((input) {
    Map data = input as Map;
    switch (data['type']) {
      case 'sendPort':
        SendPort mainToIsolateStream = data['value'];
        mainToIsolateStream.send('Message from Main');
        break;
      case 'redirect':
        if (mainToIsolateStream1 != null) {
          mainToIsolateStream1!.send(data['value']);
        }
        break;
      case 'message':
        print('Message recieved in Main: ${data['value']}');
        break;
      default:
        print('Undefined');
    }
  });
  Isolate isolate1 =
      await Isolate.spawn(myIsolate1, isolate1ToMainStream.sendPort);
  Isolate isolate2 =
      await Isolate.spawn(myIsolate2, isolate2ToMainStream.sendPort);
  await stdin.first;
  isolate1.kill(priority: Isolate.immediate);
  isolate2.kill(priority: Isolate.immediate);
  isolate1ToMainStream.close();
  isolate2ToMainStream.close();
}
//Isolate1
void myIsolate1(SendPort isolateToMainStream) {
  ReceivePort mainToIsolateStream = ReceivePort();
  mainToIsolateStream.listen((data) {
    print('Message recieved in Isolate1: $data');
  });
  isolateToMainStream.send({
    'type': 'sendPort',
    'value': mainToIsolateStream.sendPort,
  });
  isolateToMainStream.send({
    'type': 'message',
    'value': 'Message from Isolate1',
  });
}
// Isolate2
void myIsolate2(SendPort isolateToMainStream) {
  ReceivePort mainToIsolateStream = ReceivePort();
  mainToIsolateStream.listen((data) {
    print('Message recieved in Isolate2: $data');
  });
  isolateToMainStream.send({
    'type': 'sendPort',
    'value': mainToIsolateStream.sendPort,
  });
  isolateToMainStream.send({
    'type': 'message',
    'value': 'Message from Isolate2',
  });
  isolateToMainStream.send({
    'type': 'redirect',
    'value': 'Message from Isolate2 to isolate1',
  });
}
Another example where the messages when isolates are spawn are a class.
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
//Isolate operate on its own thread,
//where async operations operate on the main thread.
List<Isolate> isolates;
class Arguments {
  String name;
  String message;
  SendPort sendPort;
  Arguments({this.name, this.message, this.sendPort});
}
///Crate isolates
void start() async {
  isolates = new List();
  ReceivePort receivePort = ReceivePort();
  Arguments arguments1 = Arguments(name: 'Isolate number 1', message: '100000000', sendPort: receivePort.sendPort);
  Arguments arguments2 = Arguments(name: 'Isolate number 2', message: '10000000', sendPort: receivePort.sendPort);
  //Create two isolates
  isolates.add(await Isolate.spawn(calculation, arguments1));
  isolates.add(await Isolate.spawn(calculation, arguments2));
  //isolates.add(await Isolate.spawn(someOtherThing, someOtherArguments));
  //Listen for messages from the isolates
  receivePort.listen((data) {
    print('Data: $data');
  });
}
//Just do some heavy calculations
void calculation(Arguments arguments) {
  int _total = int.parse(arguments.message);
  for (int _a = 0; _a < _total; _a++) {
    double _tmp = sin(_a) * cos(_a);
    if (_a % (_total / 10) == 0) {
      String msg = '${arguments.name} status $_a';
      arguments.sendPort.send(msg);
    }
  }
}
//Kill the isolates
void stop() {
  for (Isolate i in isolates) {
    if (i != null) {
      i.kill(priority: Isolate.immediate);
      i = null;
      print('Killed');
    }
  }
}
void main() async {
  await start();
  print('Press enter to exit');
  await stdin.first;
  stop();
  exit(0);
}
