Design Converter
Education
Software Development Executive - II
Software Development Executive - II
Last updated on Mar 28, 2024
Last updated on Mar 28, 2024
When building a Flutter app, understanding the power of the Dart language is a significant aspect of creating a performance-oriented, clean, and agile Flutter project. The Dart language takes a unique approach to dealing with concurrent programming by using a feature known as isolates—referred to as "Dart isolates."
In this blog post, we delve deeper into the core concepts of Dart isolates, including the crucial dart:isolate library, main isolate, worker isolate, and await isolate. We also explain the concept of entry point and explore Flutter isolates. Along the way, we also share some useful code snippets and tackle some challenges related to effectively implementing Dart isolates.
Dart isolates are a way to run independent parts of an app in parallel. The term 'isolate' refers to the fact that each isolate has its memory (heap), which ensures that no isolate can access any other isolate's memory. Consequently, Dart isolates run smoothly, without any shared state.
1import 'dart:isolate'; 2 3void main() { 4 Isolate.spawn(isolateMethod, "Hello!! from Main Isolate"); 5 print('execution from main isolate'); 6} 7 8void isolateMethod(String message) { 9 print('execution from new isolate: message is : $message'); 10}
In this code snippet, we create a new isolate by calling the Isolate. spawn () method. The isolate executes the isolateMethod(). Following Dart standards, the main() method runs the main isolate, often known as the UI isolate.
Processing intensive or long-lived computations in Dart isolates—asynchronously—can significantly improve the overall performance of your Flutter app by preventing your app's UI from becoming unresponsive, offering a clean feature-sliced scalable model.
The dart:isolate library plays a crucial role in using Dart isolates. This library allows you to spawn an isolate, providing a range of functionalities to manage Dart isolates. It is fundamental for handling asynchronous tasks without blocking the app's UI thread, maintaining the smooth operation of your Flutter application.
1import 'dart:isolate'; 2 3void foo(var message){ 4 print('execution from new isolate: message is : $message'); 5} 6 7void main(){ 8 Isolate.spawn(foo,'Hello!! from Main Isolate'); 9 print('execution from main isolate'); 10}
In this code snippet, we first import the dart:isolate library. Following this, we structure our main() function to include the Isolate.spawn() method from the dart:isolate library, which calls another function, foo().
In any Dart application, execution starts in the main() function. Consistent with the standard Dart language, we call this initial function the "main isolate". In the context of Dart isolates, the isolate where the program starts is the main isolate.
It's also worth noting that all Dart code runs in an isolate, with the main() function running, by default, on the "main isolate". This main isolate is crucial—especially in Flutter apps where the Dart code handles rendering the UI. Any blocking of this primary thread could make the app's user interface unresponsive.
1void main() { 2 print('Running main isolate.'); 3 // Insert your Flutter app or Dart code here 4}
In this example, the main isolate runs a print command. In a typical Dart or Flutter app, you'd have more complex code here, often involving UI rendering for Flutter apps.
When building a Flutter app, you'll often encounter scenarios where you'll have to perform long-lived computations, like handling audio and video files, or large computations that otherwise cause the UI to become unresponsive. For these situations, Flutter isolates can be a lifesaver.
Flutter isolates run Dart code in the background, without blocking the app's main isolate, ensuring smooth operation of your Flutter app. Conceptually similar to threads, these are separate execution threads running in parallel with the main program, but with a crucial difference—they don't share memory with the main program.
Take a look at the below code where we implement a Flutter isolate:
1import 'dart:isolate'; 2 3void main() async { 4 final receivePort = ReceivePort(); 5 await Isolate.spawn(isolateEntry, receivePort.sendPort); 6 7 final sendPort = await receivePort.first; 8 final answer = ReceivePort(); 9 10 sendPort.send(['Hello from main()', answer.sendPort]); 11 print(await answer.first); 12} 13 14void isolateEntry(SendPort initialReplyTo) { 15 final receivePort = ReceivePort(); 16 initialReplyTo.send(receivePort.sendPort); 17 receivePort.listen((dynamic message) { 18 final List<dynamic> list = message; 19 final SendPort replyTo = list[1]; 20 replyTo.send('Hello from Isolate'); 21 }); 22}
In this example, we first create a new ReceivePort in the main() function. The Isolate.spawn() method then initiates the new isolate with the SendPort. Post this, we implement a ReceivePort within the new isolate and pass its SendPort back to the main isolate for further bidirectional communication.
The await isolate concept in Dart allows us to pause the flow of a function until the isolate returns a result. In other words, it allows us to 'wait' for an isolate execution to finish before moving on to the next command.
1import 'dart:isolate'; 2 3void main() async { 4 final receivePort = ReceivePort(); 5 await Isolate.spawn(echo, receivePort.sendPort); 6 7 final sendPort = await receivePort.first; 8 9 final answer = ReceivePort(); 10 sendPort.send(['Hello from main()', answer.sendPort]); 11 12 print(await answer.first); 13} 14 15echo(SendPort sendPort) { 16 final port = ReceivePort(); 17 sendPort.send(port.sendPort); 18 19 port.listen((dynamic message) { 20 final List<dynamic> list = message; 21 final messageData = list[0]; 22 print('New isolate received: ${messageData}'); 23 final SendPort replyTo = list[list.length - 1]; 24 replyTo.send("Hello from new isolate"); 25 }); 26}
In this code snippet, we're essentially building upon our previous example but adding in the await keyword - now, our function execution waits for the receivePort.first and answer.first before it can proceed.
The worker isolate is a secondary isolate spawned by the main isolate to perform computational tasks for the main isolate. The main isolate sends messages to the worker isolate and awaits responses. The worker isolate keeps the main isolate free for managing UI tasks, thus ensuring the smooth running of your Flutter app.
Here's an example in which the main isolate spawns a worker isolate and sends data to it:
1import 'dart:isolate'; 2 3void main() async { 4 ReceivePort receivePort = ReceivePort(); 5 Isolate.spawn(workerIsolate, receivePort.sendPort); 6 7 SendPort? workerSendPort; 8 receivePort.listen((data) { 9 if (data is SendPort) { 10 workerSendPort = data; 11 } else { 12 print('Main Isolate received: $data'); 13 } 14 }); 15 16 await Future.delayed(const Duration(seconds: 2)); 17 workerSendPort?.send('Hello from Main Isolate'); 18} 19 20void workerIsolate(SendPort mainSendPort) { 21 ReceivePort receivePort = ReceivePort(); 22 mainSendPort.send(receivePort.sendPort); 23 24 receivePort.listen((data) { 25 print('Worker Isolate received: $data'); 26 mainSendPort.send('Hello from Worker Isolate'); 27 }); 28}
In this example, the main isolate is spawning a worker isolate and sending the message "Hello from Main Isolate." The worker isolate is receiving the message and saying "Hello from Worker Isolate."
In the context of Dart isolates, an entry point refers to the function that a new isolate executes when it starts. The standard entry point for Dart applications is the main() function. However, when you spawn an isolate, you can specify a different entry point.
For instance:
1import 'dart:isolate'; 2 3void main() { 4 Isolate.spawn(workerIsolate, 'Hello!!'); 5 print('Execution from main isolate'); 6} 7 8void workerIsolate(String message) { 9 print('Execution from new isolate: The message is: $message'); 10}
In this example, the workerIsolate() function is the entry point for the new isolate.
In Dart, isolates are powerful tools that help us write applications that can carry out several tasks simultaneously. Isolates leverage the Dart VM's native capabilities and can run a completely standalone set of functionalities in parallel, isolated from everything else in the application.
Here's why isolates are an integral part of any robust Flutter application:
Effective Utilization of Multi-Core Devices: Most modern devices possess multi-core processors. Isolates can operate in separate threads, enabling developers to utilize multiple cores simultaneously.
Non-Blocking UI: Budding developers often face the issue of an unresponsive UI due to computationally intensive tasks being carried out on the main thread. These tasks can run on a separate thread with isolates, consequently providing smoother UI experiences.
Processing Large Data: Parsing large amounts of data can slow down applications. Isolates allow apps to handle chunks of data in parallel, speeding up processing and resulting in more efficient applications.
Safe Concurrency: Unlike threads in other programming languages, isolates don't share memory, thus avoiding issues caused by mutable shared state.
Working with Dart isolates is more comfortable with an understanding of the key components: Spawn function, SendPort, and ReceivePort.
The spawn function helps create a new isolate. The SendPort and ReceivePort allow sending and receiving messages between the main and new isolates.
Let us explore a simple use case of how we can use Dart isolates:
1import 'dart:isolate'; 2 3void main() async { 4 final response = ReceivePort(); 5 await Isolate.spawn(isolateTask, response.sendPort); 6 final sendPort = await response.first; 7 8 final answer = ReceivePort(); 9 sendPort.send(['From main isolate', answer.sendPort]); 10 11 print(await answer.first); 12} 13 14void isolateTask(SendPort mainIsolateSendPort) { 15 final port = ReceivePort(); 16 mainIsolateSendPort.send(port.sendPort); 17 18 port.listen((message) { 19 final List<dynamic> msg = message; 20 final SendPort replyTo = msg[1]; 21 22 replyTo.send('From New Isolate: ${msg[0]}'); 23 }); 24}
In this example, we are spawning a new isolate from the main isolate. The main isolate sends a message to the new isolate using the sendPort and receives a reply from the new isolate using the ReceivePort. This way, we have established communication between the main and new isolates and transmitted a message between them.
In this detailed rundown of Dart isolates, we shed light on its integral role in enhancing the performance of Flutter applications, ensuring smooth UI experiences by handling extensive computations efficiently. We covered significant ground, from understanding the basic concepts to implementing them in real-world scenarios. With their ability to handle concurrent programming, Dart isolates hold immense power. With this exposure, you can continue exploring, learning, and creating extraordinary Flutter apps. Keep growing your Dart and Flutter knowledge, and never stop building creative apps!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.