Flutter 앱을 개발하다 보면 무거운 연산(예: JSON 파싱, 이미지 처리) 때문에 UI가 멈추는 현상을 겪을 수 있다. 이럴 때 사용하는 것이 바로 Isolate이다.
이 글에서는 Isolate가 무엇인지, 언제 사용하는지, 어떻게 사용하는지, 그리고 실제 예시까지 정리한다.
Flutter는 기본적으로 단일 스레드(UI Thread)에서 실행된다. 따라서 무거운 작업을 하게 되면 UI가 멈추거나 버벅일 수 있다.
이 문제를 해결하기 위해 사용하는 것이 바로 Isolate이다.
Isolate는 Dart의 멀티스레딩 방식이며, 일반적인 스레드와 다르게 작동한다.
🔹 각
Isolate는 독립적인 메모리 공간을 가지며
🔹 서로 데이터를 직접 공유하지 않고, 메시지를 통해 통신한다.
복잡하거나 장기 실행 작업에 적합
import 'dart:isolate';
void isolateEntryPoint(SendPort sendPort) {
// 무거운 작업 실행
int result = heavyCalculation();
sendPort.send(result);
}
Future<void> runIsolate() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(isolateEntryPoint, receivePort.sendPort);
// isolate로부터 결과 받기
int result = await receivePort.first;
print("결과: $result");
}
간단한 비동기 작업 실행에 적합
import 'dart:isolate';
// 무거운 작업 함수
int heavyCalculation() {
// 예: 연산이 많은 계산
int sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
return sum;
}
Future<void> runIsolate() async {
// Isolate.run을 사용하여 작업 실행
int result = await Isolate.run(() => heavyCalculation());
print("결과: $result");
}
⚠️
Isolate.run은 Dart 3.0 이상부터 사용가능
간단한 연산 처리 (JSON 파싱 등)
import 'package:flutter/foundation.dart';
int heavyTask(int value) {
return value * 2;
}
Future<void> useCompute() async {
int result = await compute(heavyTask, 10);
print("결과: $result");
}
⚠️
compute는 함수와 파라미터 모두 직렬화 가능해야 하며, 파라미터는 하나만 받을 수 있다.
| 항목 | Isolate.spawn | Isolate.run | compute (Flutter 전용) |
|---|---|---|---|
| 도입 시기 | 오래전부터 존재 | Dart 3.0부터 도입 | Flutter에서 기본 제공 |
| 용도 | 복잡하거나 장기 실행 작업에 적합 | 간단한 비동기 작업 실행에 적합 | 간단한 연산 처리 (JSON 파싱 등) |
| 코드 구조 | ReceivePort / SendPort 필요 | async/await로 결과 처리 용이 | Future 기반으로 간단히 처리 가능 |
| 리턴 타입 | 직접 반환 없음 (포트로 수동 전달) | Future<T>로 직접 결과 반환 | Future<R> 형태로 결과 반환 |
| 코드 위치 | 반드시 top-level 함수 or static 함수 필요 | 클로저, 익명 함수 사용 가능 | top-level 함수만 허용 |
| 리소스 관리 | 수동으로 종료 필요 (kill(), exit()) | 자동으로 isolate 종료 | 내부적으로 리소스 자동 관리 |
| 플랫폼 제한 | Dart 전반에서 사용 가능 | Dart (콘솔, 서버, Flutter 등) | Flutter 프레임워크에서만 사용 가능 |
Future<List<User>> parseJson(String jsonString) async {
return compute(_parseAndDecode, jsonString);
}
List<User> _parseAndDecode(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
이미지를 필터링하거나 리사이징하는 작업에도 Isolate를 사용할 수 있다.
import 'dart:isolate';
import 'dart:io';
import 'dart:typed_data';
void imageProcessingIsolate(SendPort sendPort) async {
// 메인 Isolate에서 작업 요청을 받을 포트
final port = ReceivePort();
// 메인 Isolate에게 자신의 포트를 전달
sendPort.send(port.sendPort);
// 요청 대기
await for (final message in port) {
// message 구조: [Uint8List imageBytes, SendPort replyPort]
final Uint8List imageBytes = message[0];
final SendPort replyPort = message[1];
// 예시: 이미지의 크기를 단순히 구하는 작업 (실제 처리 로직은 더 복잡할 수 있음)
final imageSize = imageBytes.length;
// 결과 전송
replyPort.send('이미지 크기: $imageSize bytes');
// 작업이 한 번만 필요한 경우 아래로 종료
// port.close(); // 반복 수신이 필요 없다면 사용
}
}
Isolate 간 메모리는 공유되지 않는다. 데이터를 보낼 때는 직렬화 가능한 형태로 전송해야 한다.compute 는 간단한 작업에만 적합하다. 복잡한 작업에는 직접 Isolate를 생성하여 사용해야 한다.Isolate 는 리소스를 소비하므로 남용하지 않아야 하며, 재사용 혹은 적절한 관리가 필요하다.| 항목 | 내용 |
|---|---|
| Isolate란? | 독립된 메모리 공간에서 실행되는 Dart의 멀티스레드 방식 |
| 왜 사용하나? | UI 스레드를 차단하지 않기 위해 |
| 언제 사용하나? | JSON 파싱, 이미지 처리, 암호화 등 무거운 연산이 필요한 경우 |
| 어떻게 사용하나? | Isolate.spawn, ReceivePort, 혹은 Isolate.run 또는 compute() 활용 |
Flutter에서 Isolate 를 적절히 활용하면 앱의 부드러운 UX와 성능 향상을 모두 달성할 수 있다. 무조건 사용하는 것이 아니라, 적절한 시점과 상황에서 사용하는 것이 중요하다.