Flutter Background Isolates: Xử lý song song mà không ảnh hưởng UI

Flutter chạy trên một luồng chính duy nhất — main isolate — chịu trách nhiệm render UI ở 60 hoặc 120 fps và xử lý các tương tác của người dùng. Bất kỳ tác vụ nặng nào đặt lên luồng đó đều thể hiện ngay lập tức: mất frame, animation bị giật và ứng dụng có cảm giác chậm chạp.
Giải pháp của Dart là isolate: một đơn vị thực thi hoàn toàn độc lập, với bộ nhớ riêng biệt và event loop riêng. Chuyển tác vụ sang một background isolate giải phóng luồng chính để làm đúng thứ nó cần làm tốt nhất — vẽ giao diện.
Trong bài này chúng ta sẽ tìm hiểu background isolate là gì, cách hoạt động bên trong, khi nào nên dùng, và cách tích hợp vào ứng dụng Bluetooth Low Energy.
Bắt đầu thôi!
Tại sao vấn đề này tồn tại?
Dart được thiết kế theo kiểu single-threaded. Không giống Java hay Kotlin, không có shared thread hay mutex. Toàn bộ quá trình thực thi diễn ra trong một isolate — và mặc định, ứng dụng của bạn chỉ có một isolate duy nhất.
Main isolate có một ngân sách thời gian nghiêm ngặt cho mỗi frame: ~16 ms ở 60 fps hoặc ~8 ms ở 120 fps. Bất kỳ tác vụ nào mất nhiều thời gian hơn sẽ chặn bộ renderer.
1 | Timeline của main isolate (không có background isolate): |
Với background isolate:
1 | Main isolate: [frame 1] [render] [frame 2] [render] [frame 3] ← mượt mà |
Mô hình Isolate trong Dart
Một isolate trong Dart tương tự như một tiến trình OS thu nhỏ:
- Có bộ nhớ heap riêng — không chia sẻ object với các isolate khác.
- Giao tiếp duy nhất qua message passing thông qua
SendPort/ReceivePort. - Chạy trên luồng OS riêng biệt, cho phép xử lý song song thực sự trên CPU đa nhân.
1 | ┌──────────────────────────────────────────────────┐ |
Điểm quan trọng: các isolate không chia sẻ bộ nhớ. Để gửi dữ liệu giữa chúng, Dart sao chép dữ liệu (với kiểu nguyên thủy và collection đơn giản) hoặc chuyển giao (với các kiểu đặc biệt như
TransferableTypedData). Điều này loại bỏ race condition theo thiết kế.
Cách sử dụng Background Isolate
Cách 1 — compute() (đơn giản nhất)
compute là một helper của Flutter, tạo một isolate tạm thời, chờ kết quả rồi đóng isolate đó lại. Lý tưởng cho các tác vụ một lần, không có trạng thái.
1 | import 'package:flutter/foundation.dart'; |
Giới hạn quan trọng: hàm top-level (hoặc static method) truyền vào compute không thể capture closure từ môi trường của main isolate.
Cách 2 — Isolate.spawn() (kiểm soát hoàn toàn)
Cho các tác vụ dài hạn hoặc giao tiếp hai chiều, dùng Isolate.spawn trực tiếp.
1 | import 'dart:isolate'; |
Cách 3 — Isolate.run() (Dart 2.19+, cách hiện đại)
Từ Dart 2.19, Isolate.run() kết hợp điểm mạnh của cả hai: sự đơn giản của compute kèm hỗ trợ closure.
1 | import 'dart:isolate'; |
Ưu tiên dùng
Isolate.run()thay vìcompute()trong các dự án mới — ergonomic hơn và là chuẩn hiện đại của Dart.
Truy cập Plugin từ Background Isolate (Flutter 3.7+)
Trước Flutter 3.7, background isolate không thể gọi native plugin (platform channel). Đây là giới hạn lớn với ứng dụng BLE hay sensor.
Từ Flutter 3.7, điều này khả thi nhờ BackgroundIsolateBinaryMessenger:
1 | import 'dart:isolate'; |
Truyền dữ liệu hiệu quả — TransferableTypedData
Sao chép các buffer byte lớn giữa các isolate có thể tốn kém. Với dữ liệu nhị phân (như frame BLE), dùng TransferableTypedData để chuyển giao bộ nhớ mà không cần sao chép:
1 | // Trong main isolate — đóng gói để chuyển giao |
Các Trường Hợp Sử Dụng
| Trường hợp | Lý do cần isolate |
|---|---|
| Parse JSON lớn | Sẽ chặn render thread nếu làm inline |
| Nén / giải nén | Nặng CPU, mất hàng chục ms |
| Mã hóa / băm | AES, SHA256 trên buffer lớn |
| Giải mã ảnh | Trước khi đưa vào Canvas hay widget Image |
| Xử lý frame BLE | Byte thô → domain struct |
| Query SQLite nặng | Tránh độ trễ I/O trên main thread |
| Suy luận mô hình ML | TFLite chạy trên background isolate |
Isolate và Ứng Dụng BLE
Đây có lẽ là sự kết hợp thực tế nhất. Ứng dụng BLE nhận một luồng dữ liệu liên tục — thông báo characteristic, kết quả quét, frame giao thức — và cần xử lý tất cả mà không ảnh hưởng đến UI.
Vấn đề khi không có isolate
1 | BLE Plugin → Main Isolate → [parse frame] → cập nhật UI |
Giải pháp với Background Isolate
1 | // Kiến trúc đề xuất cho BLE + Isolate |
Flutter 3.7+ — Isolate gọi plugin BLE trực tiếp
1 | void _bleBackgroundIsolate(RootIsolateToken token) async { |
Thực Tiễn Tốt Nhất
1. Dùng Isolate.run() cho tác vụ một lần
1 | // ✅ Sạch, hiện đại, không cần boilerplate |
2. Không lạm dụng isolate cho tác vụ nhanh
Khởi tạo isolate tốn khoảng ~1–2 ms cộng thêm thời gian sao chép dữ liệu. Với tác vụ mất dưới ~5 ms, overhead vượt quá lợi ích.
1 | // ❌ Không đáng — quá đơn giản |
3. Tái sử dụng isolate tồn tại lâu cho stream BLE
Đừng tạo isolate mới cho mỗi frame BLE nhận được. Tạo một isolate chuyên dụng khi khởi động và giữ nó sống suốt phiên kết nối.
1 | // ✅ Một isolate xử lý nhiều frame |
4. Ưu tiên TransferableTypedData cho buffer lớn
1 | // ❌ Sao chép toàn bộ buffer |
5. Luôn gọi BackgroundIsolateBinaryMessenger.ensureInitialized() đầu tiên
Nếu isolate cần truy cập native plugin, đây phải là dòng đầu tiên nó thực thi. Mọi lời gọi plugin trước đó sẽ gây MissingPluginException.
1 | void _myIsolate(RootIsolateToken token) async { |
6. Xử lý lỗi của isolate từ main isolate
Lỗi không được bắt bên trong isolate không tự động lan truyền sang main isolate. Dùng onError khi spawn để bắt chúng.
1 | final errorPort = ReceivePort(); |
7. Luôn đóng ReceivePort khi không còn dùng
ReceivePort đang mở ngăn isolate được garbage collect. Đóng khi xong.
1 | final port = ReceivePort(); |
Tổng Kết
Background isolate là câu trả lời của Dart cho bài toán concurrency: xử lý song song thực sự không có race condition, nhờ bộ nhớ cô lập và message passing.
Với ứng dụng BLE trong Flutter, đây là công cụ không thể thiếu. Luồng dữ liệu liên tục từ thiết bị được kết nối có thể được parse, giải mã và lọc trên một isolate chuyên dụng trong khi UI hoàn toàn mượt mà. Với Flutter 3.7+, isolate đó thậm chí có thể gọi native plugin trực tiếp, loại bỏ rào cản cuối cùng cho các kiến trúc BLE nền mạnh mẽ trong Flutter.
Quy tắc vàng: nếu nó block main thread hơn một frame, hãy chuyển sang isolate.
Cuối tuần vui vẻ!