Commit 27715e1d authored by krisgiesing's avatar krisgiesing

Add an example demonstrating use of isolates (#3347)

parent f7cf0d97
name: layers-examples
assets:
- services/data.json
{
"_id": "57112806d874e9e6df7099d4",
"index": 0,
"guid": "77dc6167-2351-4a64-a603-aceaff115432",
"isActive": false,
"balance": "$1,316.41",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "brown",
"name": "Marta Hartman",
"gender": "female",
"company": "EXAMPLE",
"email": "martahartman@example.com",
"phone": "+1 (555) 555-2328",
"address": "463 Temple Court, Brandywine, Kansas, 1113",
"about": "Incididunt commodo sunt commodo nulla adipisicing duis aute enim aute minim reprehenderit aute consectetur. Eu laborum esse aute laborum aute. Tempor in cillum exercitation aliqua velit quis incididunt esse ea nisi. Cillum pariatur reprehenderit est nisi nisi exercitation.\r\n",
"registered": "2014-01-18T12:32:22 +08:00",
"latitude": 4.101477,
"longitude": 39.153115,
"tags": [
"pariatur",
"sit",
"sint",
"ex",
"minim",
"veniam",
"ullamco"
],
"friends": [
{
"id": 0,
"name": "Tricia Guerra"
},
{
"id": 1,
"name": "Paula Dillard"
},
{
"id": 2,
"name": "Ursula Stout"
}
],
"greeting": "Hello, Marta Hartman! You have 4 unread messages.",
"favoriteFruit": "strawberry"
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef void OnProgressListener(double completed, double total);
typedef void OnResultListener(String result);
// An encapsulation of a large amount of synchronous processing.
//
// The choice of JSON parsing here is meant as an example that might surface
// in real-world applications.
class Calculator {
Calculator({ this.onProgressListener, this.onResultListener, String data })
// In order to keep the example files smaller, we "cheat" a little and
// replicate our small json string into a 10,000-element array.
: _data = _replicateJson(data, 10000) {
assert(onProgressListener != null);
assert(onResultListener != null);
}
final OnProgressListener onProgressListener;
final OnResultListener onResultListener;
final String _data;
// This example assumes that the number of objects to parse is known in
// advance. In a real-world situation, this might not be true; in that case,
// the app might choose to display an indeterminate progress indicator.
static const int _NUM_ITEMS = 110000;
static const int _NOTIFY_INTERVAL = 1000;
// Run the computation associated with this Calculator.
void run() {
int i = 0;
JsonDecoder decoder = new JsonDecoder(
(dynamic key, dynamic value) {
if (key is int && i++ % _NOTIFY_INTERVAL == 0)
onProgressListener(i.toDouble(), _NUM_ITEMS.toDouble());
return value;
}
);
try {
List<dynamic> result = decoder.convert(_data);
int n = result.length;
onResultListener("Decoded $n results");
} catch (e, stack) {
print("Invalid JSON file: $e");
print(stack);
}
}
static String _replicateJson(String data, int count) {
StringBuffer buffer = new StringBuffer()..write("[");
for (int i = 0; i < count; i++) {
buffer.write(data);
if (i < count - 1)
buffer.write(',');
}
buffer.write("]");
return buffer.toString();
}
}
// The current state of the calculation.
enum CalculationState {
idle,
loading,
calculating
}
// Structured message to initialize the spawned isolate.
class CalculationMessage {
CalculationMessage(this.data, this.sendPort);
String data;
SendPort sendPort;
}
// A manager for the connection to a spawned isolate.
//
// Isolates communicate with each other via ReceivePorts and SendPorts.
// This class manages these ports and maintains state related to the
// progress of the background computation.
class CalculationManager {
CalculationManager({ this.onProgressListener, this.onResultListener })
: _receivePort = new ReceivePort() {
assert(onProgressListener != null);
assert(onResultListener != null);
_receivePort.listen(_handleMessage);
}
CalculationState _state = CalculationState.idle;
CalculationState get state => _state;
bool get isRunning => _state != CalculationState.idle;
double _completed = 0.0;
double _total = 1.0;
final OnProgressListener onProgressListener;
final OnResultListener onResultListener;
// Start the background computation.
//
// Does nothing if the computation is already running.
void start() {
if (!isRunning) {
_state = CalculationState.loading;
_runCalculation();
}
}
// Stop the background computation.
//
// Kills the isolate immediately, if spawned. Does nothing if the
// computation is not running.
void stop() {
if (isRunning) {
_state = CalculationState.idle;
if (_isolate != null) {
_isolate.kill(priority: Isolate.IMMEDIATE);
_isolate = null;
_completed = 0.0;
_total = 1.0;
}
}
}
final ReceivePort _receivePort;
Isolate _isolate;
void _runCalculation() {
// Load the JSON string. Note that this is done in the main isolate; at the
// moment, spawned isolates do not have access to Mojo services, including
// the root bundle (see https://github.com/flutter/flutter/issues/3294).
// However, the loading process is asynchronous, so the UI will not block
// while the file is loaded.
rootBundle.loadString('services/data.json').then((String data) {
if (isRunning) {
CalculationMessage message = new CalculationMessage(data, _receivePort.sendPort);
// Spawn an isolate to JSON-parse the file contents. The JSON parsing
// is synchronous, so if done in the main isolate, the UI would block.
Isolate.spawn(_calculate, message).then((Isolate isolate) {
if (!isRunning) {
isolate.kill(priority: Isolate.IMMEDIATE);
} else {
_state = CalculationState.calculating;
_isolate = isolate;
}
});
}
});
}
void _handleMessage(dynamic message) {
if (message is List<double>) {
_completed = message[0];
_total = message[1];
onProgressListener(_completed, _total);
} else if (message is String) {
_completed = 0.0;
_total = 1.0;
_isolate = null;
_state = CalculationState.idle;
onResultListener(message);
}
}
// Main entry point for the spawned isolate.
//
// This entry point must be static, and its (single) argument must match
// the message passed in Isolate.spawn above. Typically, some part of the
// message will contain a SendPort so that the spawned isolate can
// communicate back to the main isolate.
//
// Static and global variables are initialized anew in the spawned isolate,
// in a separate memory space.
static void _calculate(CalculationMessage message) {
SendPort sender = message.sendPort;
Calculator calculator = new Calculator(
onProgressListener: (double completed, double total) {
sender.send([ completed, total ]);
},
onResultListener: (String result) {
sender.send(result);
},
data: message.data
);
calculator.run();
}
}
// Main app widget.
//
// The app shows a simple UI that allows control of the background computation,
// as well as an animation to illustrate that the UI does not block while this
// computation is performed.
//
// This is a StatefulWidget in order to hold the CalculationManager and
// the AnimationController for the running animation.
class IsolateExampleWidget extends StatefulWidget {
@override
IsolateExampleState createState() => new IsolateExampleState();
}
// Main application state.
class IsolateExampleState extends State<StatefulWidget> {
String _status = 'Idle';
String _label = 'Start';
String _result = ' ';
double _progress = 0.0;
AnimationController _animation;
CalculationManager _calculationManager;
@override
void initState() {
super.initState();
_animation = new AnimationController(
duration: const Duration(milliseconds: 3600)
)..repeat();
_calculationManager = new CalculationManager(
onProgressListener: _handleProgressUpdate,
onResultListener: _handleResult
);
}
@override
Widget build(BuildContext context) {
return new Material(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new RotationTransition(
turns: _animation,
child: new Container(
width: 120.0,
height: 120.0,
decoration: const BoxDecoration(
backgroundColor: const Color(0xFF882222)
)
)
),
new Opacity(
opacity: _calculationManager.isRunning ? 1.0 : 0.0,
child: new CircularProgressIndicator(
value: _progress
)
),
new Text(_status),
new Center(
child: new RaisedButton(
child: new Text(_label),
onPressed: _handleButtonPressed
)
),
new Text(_result)
]
)
);
}
void _handleProgressUpdate(double completed, double total) {
_updateState(' ', completed / total);
}
void _handleResult(String result) {
_updateState(result, 0.0);
}
void _handleButtonPressed() {
if (_calculationManager.isRunning)
_calculationManager.stop();
else
_calculationManager.start();
_updateState(' ', 0.0);
}
String _getStatus(CalculationState state) {
switch(state) {
case CalculationState.loading:
return 'Loading...';
case CalculationState.calculating:
return 'In Progress';
case CalculationState.idle:
default:
return 'Idle';
}
}
void _updateState(String result, double progress) {
setState(() {
_result = result;
_progress = progress;
_label = _calculationManager.isRunning ? 'Stop' : 'Start';
_status = _getStatus(_calculationManager.state);
});
}
}
void main() {
runApp(
new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new IsolateExampleWidget()
}
)
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment