Isolate
Isolate는 Dart의 고급 기능 중 하나로, 단일 스레드로 실행되는 Dart 환경에서 병렬 작업을 수행할 수 있도록 도와줍니다. 이를 통해 복잡한 계산이나 I/O 작업을 별도의 스레드에서 처리하여 메인 스레드의 응답성을 유지할 수 있습니다.
Isolate 왜 필요해?
대부분의 Flutter 작업에서는 Isolate가 필요하지 않을 수도 있습니다. 하지만 복잡한 계산이나 오랜 시간이 걸리는 작업을 수행할 때, Dart는 단일 스레드로 동작하기 때문에 다른 작업을 할 수 없는 상황이 발생할 수 있습니다. 이러한 상황에서는 앱이 멈춘 것처럼 느껴져 사용성을 해칠 수 있습니다. 이런 경우 Isolate를 사용하면 메인 스레드의 응답성을 유지하면서도 복잡한 작업을 처리할 수 있습니다.
위와 같이 끊임없이 돌아가는 로딩바와 계산버튼이 존재합니다.
int calculate() {
int calculateData = 0;
for (int i = 0; i < 2000; i++) {
for (int j = 0; j < 2000; j++) {
for (int k = 0; k < 2000; k++) {
calculateData++;
}
}
}
return calculateData;
}
계산버튼을 누르면 2000의 3 제곱을 계산해서 반환해 주는 간단한 화면입니다. 해당 화면의 버튼을 눌러 어떻게 동작하는지 살펴보겠습니다.
계산버튼을 눌렀더니 끊임없이 돌아가던 로딩바는 멈추고 몇 초 뒤에 계산결과가 화면에 출력되는 모습입니다. 로딩바가 멈춘 이유는 Dart는 싱글스레드로 동작하기 때문에 2000의 3 제곱의 계산을 구하는 중에는 UI를 그릴 수 없는 상태가 되기 때문에 로딩바가 멈추는 상황이 생기게 됩니다. UI 뿐만 아니라 아무 동작도 할 수 없기 때문에 사용자는 해당 계산이 끝날 때까지 앱이 멈춰있는 불편한 사용성을 느끼게 됩니다. 만약에 Isolate를 사용하면 아래와 같이 동작하게 됩니다.
Isolate 사용예시들
Isolate 생성은 아래와 같이 간단하게 생성이 가능합니다.
Isolate.spawn(
entryPoint,
message,
);
entryPoint
- 새 Isolate가 시작될 때 실행되는 함수.
- Main Isolate와 통신하기 위한 SendPort를 받아야 함.
message
- Main Isolate가 새로 만들어지는 Isolate에 전달하는 초기 값.
- Isolate가 시작될 때 entryPoint 함수에 전달됨.
- 이를 통해 격리된 Isolate 간에 데이터 전달 가능.
1.
위의 예시와 같이 계산이 오래 걸리는 작업에 사용하는 Isolate 예시
class IsolateExecutor<T> {
IsolateExecutor({
required T Function() function,
}) {
_function = function;
}
late final Function() _function;
bool _isExecute = false;
Future<T?> executeIsolate() async {
if(_isExecute) return null;
_isExecute = true;
final ReceivePort receivePort = ReceivePort();
final isolate = await Isolate.spawn(
_isolateEntryPoint,
_IsolateMessage(
function: _function,
sendPort: receivePort.sendPort,
),
);
final result = await receivePort.first as T;
receivePort.close();
isolate.kill(priority: Isolate.immediate);
_isExecute = false;
return result;
}
void _isolateEntryPoint(_IsolateMessage message) {
final result = message.function();
message.sendPort.send(result);
}
}
class _IsolateMessage {
_IsolateMessage({
required this.function,
required this.sendPort,
});
final dynamic Function() function;
final SendPort sendPort;
}
(전체코드)
class A extends StatefulWidget {
const A({super.key});
@override
State<A> createState() => _AState();
}
class _AState extends State<A> {
int? calcData;
IsolateExecutor isolateExecutor = IsolateExecutor<int>(
function: () {
int calculateData = 0;
for (int i = 0; i < 2000; i++) {
for (int j = 0; j < 2000; j++) {
for (int k = 0; k < 2000; k++) {
calculateData++;
}
}
}
return calculateData;
},
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
calcData == null
? const CircularProgressIndicator()
: Text(
calcData.toString(),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
GestureDetector(
onTap: () async {
final result = await isolateExecutor.executeIsolate();
if(result != null) {
calcData = result;
setState(() {
});
}
},
child: Container(
width: 200,
height: 50,
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
child: const Text(
"계산",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
);
}
}
class IsolateExecutor<T> {
IsolateExecutor({
required T Function() function,
}) {
_function = function;
}
late final Function() _function;
bool _isExecute = false;
Future<T?> executeIsolate() async {
if(_isExecute) return null;
_isExecute = true;
final ReceivePort receivePort = ReceivePort();
final isolate = await Isolate.spawn(
_isolateEntryPoint,
_IsolateMessage(
function: _function,
sendPort: receivePort.sendPort,
),
);
final result = await receivePort.first as T;
receivePort.close();
isolate.kill(priority: Isolate.immediate);
_isExecute = false;
return result;
}
void _isolateEntryPoint(_IsolateMessage message) {
final result = message.function();
message.sendPort.send(result);
}
}
class _IsolateMessage {
_IsolateMessage({
required this.function,
required this.sendPort,
});
final dynamic Function() function;
final SendPort sendPort;
}
2.
class IsolateExecutor {
IsolateExecutor();
Future<T> executeIsolate<T>(Function() function) async {
final ReceivePort receivePort = ReceivePort();
final isolate = await Isolate.spawn(
_isolateEntryPoint,
_IsolateMessage(
function: function,
sendPort: receivePort.sendPort,
),
);
final result = await receivePort.first as T;
receivePort.close();
isolate.kill(priority: Isolate.immediate);
return result;
}
void _isolateEntryPoint(_IsolateMessage message) async {
final result = await message.function();
message.sendPort.send(result);
}
}
class _IsolateMessage {
_IsolateMessage({
required this.function,
required this.sendPort,
});
final dynamic Function() function;
final SendPort sendPort;
}
(전체코드)
class A extends StatefulWidget {
const A({super.key});
@override
State<A> createState() => _AState();
}
class _AState extends State<A> {
int? calcData;
IsolateExecutor isolateExecutor = IsolateExecutor();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
calcData == null
? const CircularProgressIndicator()
: Text(
calcData.toString(),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
GestureDetector(
onTap: () async {
final result = await isolateExecutor.executeIsolate<int>(
() {
int calculateData = 0;
for (int i = 0; i < 2000; i++) {
for (int j = 0; j < 2000; j++) {
for (int k = 0; k < 2000; k++) {
calculateData++;
}
}
}
return calculateData;
},
);
calcData = result;
setState(() {});
},
child: Container(
width: 200,
height: 50,
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
child: const Text(
"계산",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
);
}
}
class IsolateExecutor {
IsolateExecutor();
Future<T> executeIsolate<T>(Function() function) async {
final ReceivePort receivePort = ReceivePort();
final isolate = await Isolate.spawn(
_isolateEntryPoint,
_IsolateMessage(
function: function,
sendPort: receivePort.sendPort,
),
);
final result = await receivePort.first as T;
receivePort.close();
isolate.kill(priority: Isolate.immediate);
return result;
}
void _isolateEntryPoint(_IsolateMessage message) async {
final result = await message.function();
message.sendPort.send(result);
}
}
class _IsolateMessage {
_IsolateMessage({
required this.function,
required this.sendPort,
});
final dynamic Function() function;
final SendPort sendPort;
}
(비동기 작업도 가능하다.)
final result = await isolateExecutor.executeIsolate<int>(
() async {
int calculateData = 0;
await Future.delayed(Duration(seconds: 1));
calculateData++;
await Future.delayed(Duration(seconds: 1));
calculateData++;
await Future.delayed(Duration(seconds: 1));
calculateData++;
return calculateData;
},
);
calcData = result;
setState(() {});
Send 가능한 타입
void _isolateEntryPoint(_IsolateMessage message) async {
final result = await message.function();
message.sendPort.send(result);
}
해당코드로 새로운 Isolate에서 Main Isolate로 데이터를 보내줍니다. 보낼 수 있는 데이터 타입은 다음과 같습니다.
기본 데이터 타입: int, double, bool, String 등
컬렉션 타입:List와 Map 등
그 외 특수한 타입도 가능하다.
주의할 점
1. Isolate는 완전히 격리되어 있다.
Isolate는 독립된 실행 공간이기 때문에 변수, 객체 또는 자원을 공유할 수 없습니다. 따라서 Isolate 간에 데이터를 전달할 때는 필요한 데이터를 메시지로 보내고, 다른 Isolate에서 해당 메시지를 받아 처리하고 다시 보내는 방식으로 데이터를 전달하고 처리함으로써 각 Isolate는 자체적으로 독립적으로 실행되며, 서로 간섭하지 않습니다.
2. 웹에서는 작동하지 않습니다.
Dart Isolate는 현재 Flutter 웹 환경에서는 직접 지원되지 않습니다. 웹 환경에서는 web_worker 플러그인 또는 compute 함수를 사용해서 구현해야 합니다.
'Flutter > 기본' 카테고리의 다른 글
[Flutter] IOS App Tracking Transparency 요청 (0) | 2024.06.26 |
---|---|
[Flutter] Ios Cupertino DatePicker (0) | 2024.06.24 |
[Flutter] 세 자리마다 쉼표가 있는 TextField 구현하기 (0) | 2024.05.17 |
[Flutter] 특수상황의 DateTime UTC -> UTC 변환 (0) | 2024.05.09 |
[Flutter] TabBar Customizing Design 모음 (0) | 2024.04.26 |