Working With Low-Level Isolate APIs in Flutter

In my previous article I had explained about how to use Isolates at a high level. Using high-level APIs has some disadvantages though.

By using Low-level APIs over the High-level compute function you can get more control over the isolates. You can pause, resume and stop the Isolates at any point of time which is not possible with ‘compute’ function.

Let’s see how we can do those.

Here I am creating a custom class to sent data to Isolates. I am calling it ThreadParams.

class ThreadParams {
  ThreadParams(this.val, this.sendPort);
  int val;
  SendPort sendPort;
}

Here you can see one of the parameter is a SendPort. This is the port through with the isolate communicates

with the calling Isolate.

There is another class called ReceivePort through which the calling Isolate class gets the data back.

Start Isolate Thread

So this is how the start method will look like.

Isolate _isolate;
 bool _running = false;
 ReceivePort _receivePort;
 Capability _capability;

 void _start() async {
    if (_running) {
      return;
    }
    setState(() {
      _running = true;
    });
    _receivePort = ReceivePort();
    ThreadParams threadParams = ThreadParams(2000,     _receivePort.sendPort);
    _isolate = await Isolate.spawn(
      _isolateHandler,
      threadParams,
    );
    _receivePort.listen(_handleMessage, onDone: () {
      setState(() {
        _threadStatus = 'Stopped';
      });
    });
}

Isolate.spawn will create a new Isolate thread. The calling methods listens to the messages from the _isolate with _receivePort.listen which has a function that receives the message.

The _handleMessage can be a simple function like this.

void _handleMessage(dynamic data) {
    print(data.toString());
}

_isolateHandler method is the entry method of the _isolate and it should a either a static method or it should not a method inside a class.

So below is the _isolateHandler with the heavy operation methods that we are going to do inside the _isolate thread.

static void _isolateHandler(ThreadParams threadParams) async {
    heavyOperation(threadParams);
  }

  static void heavyOperation(ThreadParams threadParams) async {
    int count = 10000;
    while (true) {
      int sum = 0;
      for (int i = 0; i < count; i++) {
        sum += await computeSum(1000);
      }
      count += threadParams.val;
      threadParams.sendPort.send(sum.toString());
    }
  }

  static Future<int> computeSum(int num) {
    Random random = Random();
    return Future(() {
      int sum = 0;
      for (int i = 0; i < num; i++) {
        sum += random.nextInt(100);
      }
      return sum;
    });
  }

Pause, Resume and Stop Isolates

void _pause() {
  if (null != _isolate) {
    _paused ? _isolate.resume(_capability) : _capability = _isolate.pause();
    setState(() {
      _paused = !_paused;
      _threadStatus = _paused ? 'Paused' : 'Resumed';
    });
  }
}

void _stop() {
  if (null != _isolate) {
    setState(() {
      _running = false;
    });
    _receivePort.close();
    _isolate.kill(priority: Isolate.immediate);
    _isolate = null;
  }
}

Here the main thing to understand is that to pause the Isolate is that to resume an isolate, We need a Capability object which we can get while pausing the Isolate. The Isolate pause will return a Capability which can be used to resume that Isolate.

Send messages

threadParams.sendPort.send(sum.toString());

SendPort.send is used to send a message back to the Calling Isolate. By Calling Isolate i mean here the Main Isolate in which the Flutter app is running.

So thats the basics.

Complete Example

Here is the complete example.

import 'package:flutter/material.dart';
import 'dart:isolate';
import 'dart:math';
import 'dart:async';

class PerformancePage extends StatefulWidget {
  final String title = "Isolates Demo";

  @override
  _PerformancePageState createState() => _PerformancePageState();
}

class _PerformancePageState extends State<PerformancePage> {
  //
  Isolate _isolate;
  bool _running = false;
  bool _paused = false;
  String _message = '';
  String _threadStatus = '';
  ReceivePort _receivePort;
  Capability _capability;

  void _start() async {
    if (_running) {
      return;
    }
    setState(() {
      _running = true;
      _message = 'Starting...';
      _threadStatus = 'Running...';
    });
    _receivePort = ReceivePort();
    ThreadParams threadParams = ThreadParams(2000, _receivePort.sendPort);
    _isolate = await Isolate.spawn(
      _isolateHandler,
      threadParams,
    );
    _receivePort.listen(_handleMessage, onDone: () {
      setState(() {
        _threadStatus = 'Stopped';
      });
    });
  }

  void _pause() {
    if (null != _isolate) {
      _paused ? _isolate.resume(_capability) : _capability = _isolate.pause();
      setState(() {
        _paused = !_paused;
        _threadStatus = _paused ? 'Paused' : 'Resumed';
      });
    }
  }

  void _stop() {
    if (null != _isolate) {
      setState(() {
        _running = false;
      });
      _receivePort.close();
      _isolate.kill(priority: Isolate.immediate);
      _isolate = null;
    }
  }

  void _handleMessage(dynamic data) {
    setState(() {
      _message = data;
    });
  }

  static void _isolateHandler(ThreadParams threadParams) async {
    heavyOperation(threadParams);
  }

  static void heavyOperation(ThreadParams threadParams) async {
    int count = 10000;
    while (true) {
      int sum = 0;
      for (int i = 0; i < count; i++) {
        sum += await computeSum(1000);
      }
      count += threadParams.val;
      threadParams.sendPort.send(sum.toString());
    }
  }

  static Future<int> computeSum(int num) {
    Random random = Random();
    return Future(() {
      int sum = 0;
      for (int i = 0; i < num; i++) {
        sum += random.nextInt(100);
      }
      return sum;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        padding: EdgeInsets.all(20.0),
        alignment: Alignment.center,
        child: new Column(
          children: <Widget>[
            !_running
                ? OutlineButton(
                    child: Text('Start Isolate'),
                    onPressed: () {
                      _start();
                    },
                  )
                : SizedBox(),
            _running
                ? OutlineButton(
                    child: Text(_paused ? 'Resume Isolate' : 'Pause Isolate'),
                    onPressed: () {
                      _pause();
                    },
                  )
                : SizedBox(),
            _running
                ? OutlineButton(
                    child: Text('Stop Isolate'),
                    onPressed: () {
                      _stop();
                    },
                  )
                : SizedBox(),
            SizedBox(
              height: 20.0,
            ),
            Text(
              _threadStatus,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
            SizedBox(
              height: 20.0,
            ),
            Text(
              _message,
              style: TextStyle(
                fontSize: 20.0,
                color: Colors.green,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class ThreadParams {
  ThreadParams(this.val, this.sendPort);
  int val;
  SendPort sendPort;
}

That’s it.

Please clap if you find this article useful.

#flutter #mobile-apps #programming #developer

Flutter Performance - Working With Low-Level Isolate APIs in Flutter
10.40 GEEK