Isolate trong Flutter: Kẻ cứu cánh cho những tác vụ nặng

0 0 0

Người đăng: Henry Techie

Theo Viblo Asia

Như anh em đã biết, tất cả Dart code chạy trong các isolate, bắt đầu từ main isolate mặc định. Khi phát triển các ứng dụng Flutter, việc xử lý các tác vụ nặng như xử lý ảnh, tính toán phức tạp... trên main isolate này sẽ gây ra hiện tượng giật lag. Mặc dù Flutter đã cung cấp cho chúng ta Future hay Stream, nhưng về bản chất chúng vẫn được xử lý trên main isolate. Và Isolate API sinh ra chính là để giải quyết vấn đề này, nó sẽ giúp chúng ta đưa những tác vụ nặng đó xuống background worker, giúp ứng dụng trở lại với sự mượt mà vốn có. Trong bài viết này, mình sẽ giải thích cách sử dụng Isolate và lần lượt cung cấp các best practice trong quá trình sử dụng.

Isolate là gì?

Dart là ngôn ngữ single-thread, khi bạn sử dụng async, await, thực chất là nó đang chạy concurrency (đồng thời). Khi một tác vụ tốn thời gian chạy trên main thread, nó sẽ block toàn bộ UI. Và Isolate là nhân tố giúp chúng ta giải quyết bài toán parallelism (song song).

Isolate là một luồng thực thi độc lập trong Dart. Mỗi isolate có bộ nhớ Heap riêng, đảm bảo rằng không isolate nào có thể truy cập vào bộ nhớ của isolate khác, giúp cho ứng dụng chạy mượt mà mà không có trạng thái chia sẻ.

Bắt tay ngay vào code cho dễ hình dung, chúng ta sẽ đến với ví dụ đầu tiên là tạo ra một Isolate đơn giản:

import 'dart:isolate'; void main() { Isolate.spawn(isolateMethod, "Hello World from Main Isolate"); print('Main isolate');
} void isolateMethod(String message) { print('New isolate: $message');
}

Trong ví dụ trên, hàm main() sẽ chạy trên main isolate, thường được gọi là UI isolate. Chúng ta tạo một isolate mới bằng cách gọi phương thức Isolate.spawn(), nó sẽ thực thi hàm isolateMethod(). Các isolate phụ như này sẽ không truy cập được UI.

Giao tiếp giữa các Isolate

Khi cần trao đổi dữ liệu giữa các isolate, Dart đã cung cấp cho chúng ta 2 class là SendPortReceivePort để giúp các Isolate giao tiếp với nhau thông qua message. Các bạn hãy đọc qua đoạn code sau, mình sẽ giải thích chi tiết bên dưới.

void main() async { final mainReceivePort = ReceivePort(); final isolate = await Isolate.spawn(twoWayIsolate, mainReceivePort.sendPort); // Nhận SendPort từ isolate phụ mainReceivePort.listen((message) { if (message is SendPort) { print('Main: SendPort'); message.send('Hello'); } else { print('Main received: $message'); // Output: Hello to Henry isolate.kill(); } });
} void twoWayIsolate(SendPort mainSendPort) { final receivePort = ReceivePort(); // Gửi SendPort của isolate phụ về main mainSendPort.send(receivePort.sendPort); receivePort.listen((message) { if (message is String) { print('Worker received: $message'); // Output: Hello final newMessage = '$message to Henry'; mainSendPort.send(newMessage); } });
}

Trong ví dụ này, chúng ta sẽ thiết lập giao tiếp 2 chiều giữa main isolate và isolate phụ thông qua đoạn code:

// Gửi SendPort của isolate phụ về main
mainSendPort.send(receivePort.sendPort);

Ngay sau khi nhận được message là SendPort, main isolate đã gửi lời chào đến isolate phụ:

message.send('Hello');

Isolate phụ cũng đã đáp lễ bằng cách gửi lời chào ngược lại đến main isolate:

final newMessage = '$message to Henry';
mainSendPort.send(newMessage);

Các bạn có thể thấy, SendPort.send() sẽ là thằng chịu trách nhiệm gửi message, còn việc lắng nghe message sẽ được thực hiện qua ReceivePort.listen().

Sử dụng compute cho tác vụ đơn giản

Flutter cung cấp hàm compute để chạy hàm trong isolate mới với chỉ với 1 dòng code đơn giản như sau:

Future<void> fetchData() async { final result = await compute(_heavyProcessing, 1000000); print('Kết quả: $result');
} // Hàm này sẽ chạy trong isolate riêng
int _heavyProcessing(int iterations) { int sum = 0; for (int i = 0; i < iterations; i++) { sum += i; } return sum;
}

Ưu điểm của computetự động quản lý isolate, nó sẽ được hủy ngay sau khi hoàn thành.

Tuy nhiên, có ưu thì cũng phải có nhược, giới hạn của compute là chỉ truyền được 1 tham số, nếu muốn nhiều tham số hơn thì bạn phải đóng gói nó vào Map hoặc List. Ngoài ra, compute cũng không thể giao tiếp 2 chiều như Isolate.spawn trong ví dụ trên.

Best Practices

Chỉ dùng isolate cho các tác vụ tốn nhiều CPU

Với các network request, chúng ta có thể dùng http client bình thường. Chỉ nên dùng nó trong các trường hợp tốn nhiều CPU như xử lý ảnh.

// Nên
compute(imageProcessing, imageData); // Không nên
compute(networkRequest, url);

Tránh tạo isolate liên tục

Việc tạo isolate mới liên tục gây tiêu tốn CPU và bộ nhớ, đồng thời việc khởi tạo isolate cũng khá chậm, mất khoảng 30ms mỗi lần. Vậy nên, việc sử dụng Worker Pool sẽ giúp tái sử dụng các isolate đã tạo.

Sử dụng kiểu dữ liệu đơn giản khi truyền qua port

Do các isolate không chia sẻ bộ nhớ nên dữ liệu truyền giữa các isolate phải được serialized/deserialized. Nếu sử dụng kiểu dữ liệu phức tạp sẽ làm tăng thời gian chuyển đổi và tốn bộ nhớ khi copy dữ liệu.

// Nên
sendPort.send({'id': 1, 'value': 42}); // Không nên
sendPort.send(MyComplexClass());

Dừng isolate khi không cần

Khi sử dụng Isolate.spawn, nó sẽ không tự động giải phóng khi không dùng nữa. Việc để isolate chạy ngầm sẽ gây tốn CPU/bộ nhớ và có thể là cả memory leak. Vậy nên chúng ta có thể sử dụng hàm Isolate.kill để dừng khi hoàn thành.

isolate.kill(priority: Isolate.immediate);

Kết Luận

Isolate là công cụ mạnh mẽ để xử lý các tác vụ nặng trong Flutter mà không ảnh hưởng đến UI. Tuy có độ phức tạp nhất định, nhưng khi sử dụng đúng cách bằng việc kết hợp với compute và các thư viện hỗ trợ, bạn có thể tạo ra ứng dụng mượt mà ngay cả khi xử lý nghiệp vụ phức tạp.

Reference

🔔 Blog: henrytechie.com

☕️ Facebook: Henry Techie

☁️ TikTok: @henrytechie

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 300

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?

Lời mở đầu. Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter.

1 1 360

- vừa được xem lúc

Flutter Animation: Creating medium’s clap animation in flutte Part II

Trong phần 1 mình đã giới thiệu với các bạn cơ bản về Animation trong Flutter. Score Widget Size Animation.

0 0 70

- vừa được xem lúc

Flutter - GetX - Using GetConnect to handle API request (Part 4)

Giới thiệu. Xin chào các bạn, lại là mình với series về GetX và Flutter.

0 0 370

- vừa được xem lúc

StatefulWidget và StatelessWidget trong Flutter

I. Mở đầu. Khi các bạn build một ứng dụng với Flutter thì Widgets là thứ không thể thiếu đúng không ạ. Và 2 loại Widget không thể thiếu đó là StatefullWidget và StatelessWidget.

0 0 157

- vừa được xem lúc

Tìm hiểu về Riverpod - Provider nhưng không hắn :v

Trong Flutter có rất nhiều các quản lý state: Provider, Bloc, GetX, Redux,... khó mà nói cái nào tốt hơn cái nào. Tuy nhiên nếu bạn đã làm quen với Provider thì không ngại để tìm hiểu thêm về Riverpod. Một bản nâng cấp của Provider. Nếu bạn để ý thì cái tên "Riverpod" là các chữ cái của "Provider" đ

0 0 73