Multithreading in Flutter
Multithreading is a technique used in software development to improve performance and responsiveness by allowing multiple threads or processes to run concurrently. In the context of Flutter, multithreading can be used to offload CPU-intensive or long-running tasks from the main thread, which is responsible for handling user input and updating the UI.
Flutter provides built-in support for multithreading using isolates, which are lightweight threads that run in separate memory spaces from the main thread. Isolates can communicate with each other using message passing, but they cannot share memory or state.
Using isolates in Flutter
To use isolates in Flutter, we can use the Isolate.spawn method to create a new isolate and pass a callback function to run in the isolate. Here is an example of how to use Isolate.spawn:
void _runInBackground() async {
final ReceivePort receivePort = ReceivePort();
await Isolate.spawn(_backgroundTask, receivePort.sendPort);
receivePort.listen((message) {
print('Received message: $message');
});
}
void _backgroundTask(SendPort sendPort) {
// Do some background task
final result = 42;
// Send result back to the main thread
sendPort.send(result);
}
In this example, we create a new isolate using Isolate.spawn and pass in a callback function _backgroundTask. The _backgroundTask function does some background task and then sends the result back to the main thread using the SendPort object passed in as a parameter.
On the main thread, we create a ReceivePort object to receive messages from the isolate. We then listen for messages on the ReceivePort and print them out when they are received.
Using isolates with the Compute function
While isolates provide a way to offload CPU-intensive tasks from the main thread, they can be a bit cumbersome to use directly. Fortunately, Flutter provides a simpler way to use isolates with the compute function, which is a high-level API that allows us to execute a function in an isolate and receive the result as a Future.
Here is an example of how to use the compute function with a Fibonacci function:
import 'dart:async';
import 'package:flutter/foundation.dart';
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
void main() async {
final result = await compute(fibonacci, 40);
print('Fibonacci result: $result');
}
In this example, we define a simple fibonacci function that calculates the nth Fibonacci number recursively. We then call the compute function with the fibonacci function and an argument of 40. The compute function runs the fibonacci function in a separate isolate and returns a Future that resolves to the result.
Using isolates with a ComputeService
Flutter provides a convenient way to use isolates with a ComputeService. A ComputeService is a utility class that simplifies the process of running a function in an isolate and returning the result to the main thread.
Here is an example of how to use the ComputeService to run an expensive computation:
import 'dart:async';
import 'package:flutter/foundation.dart';
void main() async {
final result = await ComputeService.compute(fibonacci, 40);
print('Fibonacci result: $result');
}
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
class ComputeService {
static Future<dynamic> compute(Function function, dynamic arg) async {
final response = Completer<dynamic>();
final isolate = await Isolate.spawn(_spawnIsolate);
final sendPort = ReceivePort();
isolate.addOnExitListener(sendPort.sendPort);
isolate.ping(sendPort.sendPort);
sendPort.listen((message) {
if (message is SendPort) {
message.send(arg);
} else {
response.complete(message);
}
});
return response.future;
}
static void _spawnIsolate(SendPort sendPort) {
final receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
final result = Function.apply(message, [40]);
sendPort.send(result);
});
}
}
In this example, we define a compute method that takes a function and an argument and returns a Future that resolves to the result of running the function in a separate isolate.
The compute method first creates a Completer object to hold the response from the isolate. It then spawns a new isolate using the _spawnIsolate function and listens for messages on a ReceivePort object.
In the _spawnIsolate function, we create a new ReceivePort object to receive messages from the main thread. We then send the SendPort object of the ReceivePort back to the main thread, so that it can send messages to the isolate.
On the main thread, when a message is received on the SendPort, we check if it is a SendPort object. If it is, we send the argument to the isolate using the SendPort. If it is not, we complete the Completer object with the result of running the function in the isolate.
To use the ComputeService, we can call the compute method with a function and an argument:
void main() async {
final result = await ComputeService.compute(fibonacci, 40);
print('Fibonacci result: $result');
}
In this example, we call the compute method with the fibonacci function and an argument of 40. The ComputeService spawns an isolate, runs the fibonacci function in the isolate, and returns the result as a Future. We then print the result to the console.
Conclusion
In this article, we have learned how to use isolates in Flutter to run expensive operations without blocking the user interface. We have also learned about the ComputeService, a convenient utility class provided by Flutter for running functions in isolates. By using isolates and the ComputeService, we can build high-performance Flutter applications that provide a smooth user experience, even for computationally intensive tasks.