Understanding Flutter's Multithreading: Isolates, Event Queues, async/await, and Platform Runners
This article explains Flutter's multithreading model, comparing isolates and event queues to iOS GCD, detailing async/await, Future, coroutine concepts, and the roles of Platform, UI, GPU, and IO runners, with code examples illustrating isolate communication and asynchronous network requests.
Since the release of Flutter 1.0 at the end of 2018, the framework has seen explosive growth in China, with many companies such as Xianyu, Douyin and Toutiao adopting it for cross‑platform development. To truly master Flutter, developers must understand its multithreading characteristics rather than merely invoking APIs.
Event Queue
Flutter processes tasks on a single thread by default. Similar to iOS, Dart has an event loop and message queue , but the thread in Dart is called an isolate . When the application starts, the main function runs in the main isolate .
Each isolate contains an event loop and two queues: an event queue (handling I/O, rendering, gestures, etc.) and a microtask queue (higher‑priority tasks). The microtask queue is processed first; only when it is empty does the event queue run. If a microtask blocks the event queue, rendering and gesture response can be delayed, so time‑consuming work should be placed in the event queue.
async / await
In Dart, the keywords async , await and Future form the core of asynchronous programming. An async function returns a Future . If no explicit return value is provided, the function returns a Future<null> . Using await pauses the current isolate until the awaited Future completes, then resumes execution.
Future<Response> dataReqeust() async {
String requestURL = 'https://jsonplaceholder.typicode.com/posts';
Client client = Client();
Future<Response> response = client.get(requestURL);
return response;
}
Future<String> loadData() async {
Response response = await dataReqeust();
return response.body;
}The above code shows how async and await are used to perform a network request without blocking the UI.
Isolate Communication
An isolate has its own memory; communication occurs via port objects. The following example spawns a new isolate, sends a URL, and receives parsed JSON data.
loadData() async {
// Create a ReceivePort and spawn a new isolate
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// Get the SendPort of the new isolate
SendPort sendPort = await receivePort.first;
// Send a request and wait for the result
List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
print('dataList $dataList');
}
static dataLoader(SendPort sendPort) async {
ReceivePort receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
await for (var msg in receivePort) {
String requestURL = msg[0];
SendPort callbackPort = msg[1];
Client client = Client();
Response response = await client.get(requestURL);
List dataList = json.decode(response.body);
callbackPort.send(dataList);
}
}
Future sendReceive(SendPort sendPort, String url) {
ReceivePort receivePort = ReceivePort();
sendPort.send([url, receivePort.sendPort]);
return receivePort.first;
}Isolates communicate asynchronously through SendPort and ReceivePort , avoiding shared‑memory contention.
Coroutines
In Dart, async / await are syntactic sugar for coroutines. A coroutine (or coroutine ) is a lightweight execution unit smaller than a thread. When a coroutine reaches an await , the current context is saved and the scheduler can run other I/O tasks. Once the awaited operation finishes, execution resumes at the saved point.
Task Scheduling
Concurrency in Flutter is achieved by non‑blocking I/O and event notifications, while parallelism uses multiple isolates or thread pools. The Isolate model isolates memory, eliminating the need for locks.
Embedder
The Embedder layer embeds Flutter into each platform, handling native plugins, thread management, and the event loop. It creates four runners: UI Runner, GPU Runner, IO Runner, and a shared Platform Runner.
Runners
Platform Runner : analogous to the main thread on iOS; all non‑CPU‑intensive tasks should run here.
UI Runner : executes the root isolate’s UI code, processes widget layout, and submits layer trees to the engine.
GPU Runner : manages GPU resources and submits the layer tree to the Skia renderer.
IO Runner : handles I/O‑heavy work such as image decoding; it can forward tasks to the GPU Runner when needed.
Understanding these components helps developers decide where to place heavy computations to keep the UI responsive.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.