Skip to content

Instantly share code, notes, and snippets.

@mingsai
Last active March 20, 2022 22:37
Show Gist options
  • Save mingsai/2643c29d16888b8d47d42e5b342e6ecc to your computer and use it in GitHub Desktop.
Save mingsai/2643c29d16888b8d47d42e5b342e6ecc to your computer and use it in GitHub Desktop.
Use compute function on non-web apps as shortcut to spawn isolates
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const appTitle = 'Isolate Demo';
return const MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
return PhotosList(photos: snapshot.data!);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class PhotosList extends StatelessWidget {
const PhotosList({Key? key, required this.photos}) : super(key: key);
final List<Photo> photos;
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Spawn an isolate, read multiple files, send their contents to the spawned
// isolate, and wait for the parsed JSON.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:async/async.dart';
const filenames = [
'json_01.json',
'json_02.json',
'json_03.json',
];
void main() async {
await for (final jsonData in _sendAndReceive(filenames)) {
print('Received JSON with ${jsonData.length} keys');
}
}
// Spawns an isolate and asynchronously sends a list of filenames for it to
// read and decode. Waits for the response containing the decoded JSON
// before sending the next.
//
// Returns a stream that emits the JSON-decoded contents of each file.
Stream<Map<String, dynamic>> _sendAndReceive(List<String> filenames) async* {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJsonService, p.sendPort);
// Convert the ReceivePort into a StreamQueue to receive messages from the
// spawned isolate using a pull-based interface. Events are stored in this
// queue until they are accessed by `events.next`.
final events = StreamQueue<dynamic>(p);
// The first message from the spawned isolate is a SendPort. This port is
// used to communicate with the spawned isolate.
SendPort sendPort = await events.next;
for (var filename in filenames) {
// Send the next filename to be read and parsed
sendPort.send(filename);
// Receive the parsed JSON
Map<String, dynamic> message = await events.next;
// Add the result to the stream returned by this async* function.
yield message;
}
// Send a signal to the spawned isolate indicating that it should exit.
sendPort.send(null);
// Dispose the StreamQueue.
await events.cancel();
}
// The entrypoint that runs on the spawned isolate. Receives messages from
// the main isolate, reads the contents of the file, decodes the JSON, and
// sends the result back to the main isolate.
Future<void> _readAndParseJsonService(SendPort p) async {
print('Spawned isolate started.');
// Send a SendPort to the main isolate so that it can send JSON strings to
// this isolate.
final commandPort = ReceivePort();
p.send(commandPort.sendPort);
// Wait for messages from the main isolate.
await for (final message in commandPort) {
if (message is String) {
// Read and decode the file.
final contents = await File(message).readAsString();
// Send the result to the main isolate.
p.send(jsonDecode(contents));
} else if (message == null) {
// Exit if the main isolate sends a null message, indicating there are no
// more files to read and parse.
break;
}
}
print('Spawned isolate finished.');
Isolate.exit();
}
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Read the file, spawn an isolate, send the file contents to the spawned
// isolate, and wait for the parsed JSON.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
const filename = 'json_01.json';
Future<void> main() async {
final filename = 'json_01.json';
final jsonData = await _spawnAndReceive(filename);
print('Received JSON with ${jsonData.length} keys');
}
// Spawns an isolate and sends a [filename] as the first message.
// Waits to receive a message from the the spawned isolate containing the
// parsed JSON.
Future<Map<String, dynamic>> _spawnAndReceive(String fileName) async {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJson, [p.sendPort, fileName]);
return (await p.first) as Map<String, dynamic>;
}
// The entrypoint that runs on the spawned isolate. Reads the contents of
// fileName, decodes the JSON, and sends the result back the the main
// isolate.
void _readAndParseJson(List<dynamic> args) async {
SendPort responsePort = args[0];
String fileName = args[1];
final fileData = await File(fileName).readAsString();
final result = jsonDecode(fileData);
Isolate.exit(responsePort, result);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment