Last active
March 20, 2022 22:37
-
-
Save mingsai/2643c29d16888b8d47d42e5b342e6ecc to your computer and use it in GitHub Desktop.
Use compute function on non-web apps as shortcut to spawn isolates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}, | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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