Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Isolating Flutter into multithreading effectively

Isolating Flutter into multithreading effectively

- Exploring Rx Dart
- Flutter/Dart's threading model and asynchronous apis like async/await
- How isolates Api resolve parallel processing.
- Advanced Isolates usage.
- Benchmarks while using Isolates.
- Implementing a Scheduler for idiomatic thread hopping in Rx-Dart.

Avatar for Anvith Bhat

Anvith Bhat

August 30, 2019
Tweet

Other Decks in Technology

Transcript

  1. –A wise man “True judge of courage is when you

    get to stand between a man and his food”
  2. Exploring Rx Dart 1. Observables can be subscribed only once.

    2. onListen (doOnSubscribe) called once. 3. No Schedulers ¯\_(ツ)_/¯

  3. Android to Flutter override fun updateUi(state: ViewState) {
 
 


    
 
 
 
 
 } @override Widget build(BuildContext context) { return Container(child: _content(context)); } 
 Widget _content(BuildContext context) { 
 
 
 
 
 
 
 
 
 
 
 
 } 
 
 if (state.isOnboardingCompleted) { showPermissionDialog() }
 progress.visibility = if(state.isLoading){ View.VISIBLE } else { View.GONE } if(data.isOnboardingCompleted){ showPermissionDialog(); } if (data.state == LoadState.LOADING) { return Center( child: CircularProgressIndicator(), ); } else { return _Body(); }
  4. @override Widget build(BuildContext context) { return Container(child: _content(context)); } 


    Widget _content(BuildContext context) { 
 
 
 
 
 
 
 
 
 
 
 
 } 
 
 if(data.isOnboardingCompleted){ showPermissionDialog(); } @override Widget build(BuildContext context) { return Container(child: _content(context)); } 
 Widget _content(BuildContext context) { 
 
 
 
 
 
 
 
 
 
 
 
 } 
 
 if(data.isOnboardingCompleted){ Future(()=>
 showPermissionDialog()); }
  5. Flutter Framework • Flutter engine does not create its own

    threads or manage them Platform Task
 Runner UI Task 
 Runner IO Task 
 Runner GPU Task 
 Runner Embedder
  6. Load Costs Accessible Load Behaviour Platform Task Runner Yes Gesture

    events dropped 
 and app termination UI Task Runner Yes Frame dropping/jank IO Task Runner No Delayed Futures GPU Task Runner No Frame dropping/jank
  7. Flutter Thread Quirks • Threading Api is implicit.
 • Futures

    and Async/Await solve problems of asynchrony not computation.
  8. • Don't share memory. • Data wired is copied •

    Parents can limit abilities of isolates. IsolatesAttributes
  9. How do I make em 
 Future<int> startIsolate() async {

    
 
 
 
 
 
 
 }
 
 1. Isolate.spawn(runnable, runnable_param) //Create a Port for communication
 ReceivePort receivePort = ReceivePort(); //Create the isolate
 await Isolate.spawn(computeMatrix, 
 receivePort.sendPort); //Pick first item in stream
 return await receivePort.first; //Callback void computeMatrix(SendPort port) { var result = heavilyComputedValue(); port.send(result) }
 void onButtonClick() { startIsolate().then((computedValue) {
 setState(() { computedText = "Matrix result is 
 ${computedValue}"; });
 }); }
  10. How do I make em void createRestrictedIsolate(Isolate main){
 
 //Proxy

    Isolate restrictedIsolate = new Isolate(main.controlPort);
 untrustedCode(restrictedIsolate); } 1. Isolate.spawn(callback, callback_param) 2. via Constructor
  11. How do I make em 1. Isolate.spawn(callback, callback_param) 3. spawnUri(Uri

    uri, List<String> args) Future<void> executeFileContent(Isolate main) async{ 
 var args =List<String>();
 args.add("type=monthly_data");
 var isolate = await Isolate.spawnUri('./periodic_sync.dart',args)
 
 } 2. via Constructor
  12. Processing Large Data • Flutter copies data on the parent

    isolate. • TransferableTypedData creates cross isolate transferrable buffer.
 
 
 
 

  13. void sendData(SendPort replyPort){
 
 
 
 
 
 
 }

    void receiveData(ReceivePort receivePort) async { 
 
 
 } dynamic x = new ByteData(2 * 1024 * 1024); for (int i = 0; i < 4; i++) { x.setInt8(i, i); } replyPort.send(TransferableTypedData.fromList([x.buffer.asUint8List()])); var first = await receivePort.first as TransferableTypedData; var data = first.materialize().asByteData();
  14. Benchmarks • Spawn time for a typical isolate varies between

    10-80ms • Each isolate is allocated an independent heap. Around 5-6 mb on a higher end phone. • Isolate creation times on android are almost 8-10x of ios.
  15. Isolate Runner • Allows one to check if the runner

    is still alive. • Specify timeouts via. run function Wrapper around creation of Isolates. Future<R> run<R, P>(FutureOr<R> function(P argument), P argument, {Duration timeout}) {
  16. Load Balancers • Create and Manage Isolate runner pool.
 var

    balancer = LoadBalancer.create(POOL_SIZE, IsolateRunner.spawn) 
 balancer.run(heavyCompute, heavyComputeArg, estimatedLoad)
  17. Isolate + Streams getMatrixObservable() .asyncMap(startIsolate)
 .listen((data){ print("Me gots data") });

    - asyncMap
 Future<int> startIsolate(int a) async { ReceivePort receivePort = ReceivePort();
 Isolate isolate = await Isolate.spawn(isolateTask, receivePort.sendPort); return await receivePort.first; }
  18. Isolate + Streams - fromFuture - asyncMap
 Future<int> startIsolate() async

    { ReceivePort receivePort = ReceivePort();
 Isolate isolate = await Isolate.spawn(isolateTask, receivePort.sendPort); return await receivePort.first; } Observable
 .fromFuture(startIsolate())
 .listen((data){ print("I come from the future: $data") });;
  19. An Idiomatic Spell • Stream transformers operator 
 could help

    out Observable.just(value) .observeOn(Schedulers.io())

  20. class IoScheduler<S, T> implements StreamTransformer<S, T> {
 
 
 


    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 } if (_loadBalancer == null) { _loadBalancer = await LoadBalancer.create(POOL_SIZE, IsolateRunner.spawn); } // Create isolate pool
 static Future<void> initLoadBalancer() async { // Isolate pool state
 
 static LoadBalancer _loadBalancer;
 static const int POOL_SIZE = 6; }
  21. class IoScheduler<S, T> implements StreamTransformer<S, T> {
 
 
 


    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 } // Initialise stream handlers
 
 IoScheduler(S isolateCode(T value)) { } _transformer = isolateCode; _controller = new StreamController<T>( onListen: _onListen
 ); Function _transformer; Future<void> initLoadBalancer() async { ..}
  22. class IoScheduler<S, T> implements StreamTransformer<S, T> {
 
 
 


    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 } // Initialise stream handlers
 
 IoScheduler(S isolateCode(T value)) {..} Function _transformer; } void _onListen() { _subscription = _stream.listen(
 onData, onError: _controller.addError ); Future<void> initLoadBalancer() async { ..}
  23. class IoScheduler<S, T> implements StreamTransformer<S, T> { 
 
 


    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 } ..} void _onListen() { void onData(S data) { initLoadBalancer().then((val) {
 
 
 
 
 
 }); } _loadBalancer.run(_transformer, data).then((transformed) { _controller.add(transformed as T); }, onError: (error) { throw error; });
 // Initialise stream handlers
 
 IoScheduler(S isolateCode(T value)) { ..} Function _transformer; Future<void> initLoadBalancer() async { ..}
  24. The Final Spell Observable getFibonnaciValue(int value) {
 
 return Observable.just(value)

    .transform(IoScheduler<int, int>(calculateFib));
 } int calculateFib(int n) { if (n <= 1) return n; else return calculateFib(n - 1) + calculateFib(n - 2); }
  25. The "not so good" parts • Copy overheads* • Isolate

    creation times are sporadic. • Doesn't guarantee true parallelism all the time. Since they're backed by VM thread pool.
  26. Takeaways • Avoid isolates for simpler tasks like object transformations.

    • Use Isolates for json decoding, encryption and image processing. • Figure out a pool size that works well for your app. Around 5-6 isolates are good enough for most apps. • Profile your app.