paint-brush
An Introduction to Dart Code and Isolateby@gbksoft
8,206 reads
8,206 reads

An Introduction to Dart Code and Isolate

by GBKSOFTDecember 31st, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Any Dart code is executed in Isolate. Isolate is an isolated environment, inside which there is memory allocated to it and its EventLoop.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - An Introduction to Dart Code and Isolate
GBKSOFT HackerNoon profile picture

Dart was originally a single-threaded programming language. However, many have heard of Isolate. They are often asked about in Flutter developer interviews. For this reason alone, it is worth studying this topic in a little more detail.

Today we will figure out what they are and how they can help us create responsive interfaces that work with 60 fps.

What is Isolate?

Any Dart code is executed in Isolate. Isolate is an isolated environment, inside which there is memory allocated to it and its EventLoop. Each Isolate is single-threaded and can only manage the memory and EventLoop allocated for it. You cannot control the memory of another Isolate.

How do different Isolates communicate?

Communication between Isolates occurs through the transfer of messages between ports.

How do I start using Isolates in my applications?

So, to use this powerful tool, we need to include the Dart library: isolates

If we look at the documentation, we will see a rather small API that gives us the following main classes to work with:

  • Capability - A non-counterfeit object that returns the same when passing through other isolates. That is, we can receive this object in no other way, except by sending some object to the isolate and receiving it back.
  • Isolate - Isolate or isolated context of the execution of Dart code.
  • RecievePort is what allows our isolates to communicate with each other through messaging. Inherits the Stream interface. It should be noted that RecievePort is a Single subscription stream, which means it can only have one listener. It has a sendPort getter that returns an instance of the SendPort class, which is discussed later.
  • SendPort is a class that can send messages to RecievePort. SendPort inherits the Capability interface.

When should I use Isolate?

So there are two surefire cases when you should resort to Isolate.

  1. Suppose you need to complete a sizeable one-time task. For example, de- / serialization of data, receiving a large response from the server, some complex mathematical calculations (Fibonacci number), etc.
  2. You can isolate someone else's poorly optimized code so that you do not rewrite it. Thus, you will protect yourself from a drop in your application's performance due to the fault of any massive libraries or non-optimal code of another person.

Basic example

import 'dart:isolate';


import 'package:flutter/material.dart';


void main() {

 runApp(MyApp());

}


class MyApp extends StatelessWidget {

 @override

 Widget build(BuildContext context) {

   return MaterialApp(

     home: MyHomePage(),

   );

 }

}


class MyHomePage extends StatefulWidget {

 @override

 _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

 Isolate isolate;

 ReceivePort receivePort;


 @override

 void initState() {

   super.initState();

   spawnNewIsolate();

 }


 void spawnNewIsolate() async {

   receivePort = ReceivePort();

   try {

     isolate = await Isolate.spawn(sayHello, receivePort.sendPort);

     print("Isolate: $isolate");

     receivePort.listen((dynamic message) {

       print('New message from Isolate: $message');

     });

   } catch (e) {

     print("Error: $e");

   }

 }


 //spawn accepts only static methods or top-level functions

 static void sayHello(SendPort sendPort) {

   sendPort.send("Hello from Isolate");

 }


 @override

 void dispose() {

   super.dispose();

   receivePort.close();

   isolate.kill();

 }


 @override

 Widget build(BuildContext context) {

   return Scaffold(

     appBar: AppBar(

       title: Text("Isolate Demo"),

     ),

     body: Center(),

   );

 }

}

Compute

So we figured out how to spawn a new isolate. Now let's look at more convenient ways of working with isolates, and the first of them will be the built-in wrapper over Isolate Api in Flutter - Compute.

Compute slightly expands the capabilities of working with isolates in Flutter and takes on the following responsibilities:

  • Isolate creation
  • Transfer task to isolate
  • Completing this task
  • Returning a response
  • Closing ports
  • Isolate destruction

Thus, all you need to use the compute function is to pass the first argument to the function that you want to execute in the isolate, and the second argument to pass those arguments that should go to the executable function.

Basic example

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';


void main() {

 runApp(MyApp());

}


class MyApp extends StatelessWidget {

 @override

 Widget build(BuildContext context) {

   return MaterialApp(

     home: MyHomePage(),

   );

 }

}


class MyHomePage extends StatefulWidget {

 @override

 _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

 @override

 void initState() {

   super.initState();

   createComputeFunction();

 }


 void createComputeFunction() async {

   String answer;

   answer = await compute(sayHelloFromCompute, 'Hello');

   print("Answer from compute: $answer");

 }


 static String sayHelloFromCompute(String string) {

   return string;

 }


 @override

 Widget build(BuildContext context) {

   return Scaffold(

     appBar: AppBar(

       title: Text("Isolate Demo"),

     ),

     body: Center(),

   );

 }

}

Third-party solutions

We have already mastered the skills of working with isolates and know how to use the compute function.

Today there are two third-party libraries for working with isolates.

computer - https://pub.dev/packages/computer 

  • Creating a limited number of workers
  • Distribution of tasks between the created workers
  • Task queue management

worker_manager - https://pub.dev/packages/worker_manager

  • Creation of a pool of isolates, which itself is replenished with new ones when one of the isolates is destroyed
  • Works through streams
  • Allows you to set work timeouts (kills isolate after a specified time in case the task is too resource-intensive)
  • Ability to stop calculations

You can already familiarize yourself with them in more detail and see examples of work in the documentation.

Representative example

However, we have not yet looked at the most illustrative example showing the importance of performing complex operations in separate isolates.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
 runApp(MyApp());
}
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: MyHomePage(),
   );
 }
}
class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
 AnimationController rotationController;
 Animation animation;
 List<int> results = [];
 @override
 void initState() {
   super.initState();
   rotationController = AnimationController(
     duration: const Duration(seconds: 5),
     vsync: this,
   )..addListener(() => setState(() {}));
   animation = Tween(begin: 0.0, end: 1.0).animate(rotationController);
   rotationController.forward(from: 0.0);
   //loop the animation for clarity
   animation.addStatusListener((status) {
     if (status == AnimationStatus.completed) {
       rotationController.repeat();
     }
   });
 }
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text("Isolate Demo"),
     ),
     body: Column(
       children: [
         SizedBox(
           height: 100,
         ),
         Center(
           child: RotationTransition(
             turns: animation,
             child: Container(
               width: 200,
               height: 200,
               color: Colors.orange,
             ),
           ),
         ),
         SizedBox(
           height: 100,
         ),
         RaisedButton(
           onPressed: () {
             setState(() {
               final result = fib(40);
               print(result);
               results.add(result);
             });
           },
           child: Text("fib(40) in main thread"),
         ),
         RaisedButton(
           onPressed: () async {
             final result = await compute(fib, 40);
             setState(() {
               results.add(result);
             });
           },
           child: Text("fib(40) in isolate"),
         ),
         Text("Number of results: ${results.length.toString()}")
       ],
     ),
   );
 }
}
int fib(int n) {
 if (n < 2) {
   return n;
 }
 return fib(n - 2) + fib(n - 1);
}

Additional resources

About the Author

Sergey Vedmediev

Flutter Developer at GBKSOFT

Having started my career as a front-end developer; at some point, I got to start learning Flutter. Despite my moderate experience with it, I was able to switch to building beautiful cross-platform applications quickly. I think this technology, and in particular, the Dart language is much more promising and exciting.

FAQ:

What is Isolate?

Isolate - an isolated environment within which Dart code is executed with its own allocated memory (heap) and its own (separate) EventLoop.

What are they needed for?

They are needed to perform complex operations in separate threads and not affect performance in the main thread (main/ui-thread).

How do I use them?

The dart:isolate library in Dart or the compute function in Flutter or third-party Flutter libraries.