우리가 보통 소요시간이 있거나, 서버를 통해 수행하는 작업은 동기적(synchronous)으로 처리하기 어렵기 때문에 비동기적(asynchronous)으로 처리하여 그 사이에 미리 UI를 그리거나 다른 작업을 할 수 있도록 한다.
하지만 큰 작업을 실행할 경우, 화면이 멈추는 현상이 발생한다.
왜일까?
Flutter가 사용하는 Dart언어는 싱글스레드로 작동한다
처리하는 모든 작업이 하나의 스레드에서 동작하기 때문에, 아무리 비동기 작업이라고 해도 UI에 영향을 주는 것이다.
그렇다면, 메인 스레드와 완전히 독립적으로 작업을 실행할 수 없을까?
Flutter는 모든 작업이 메인Isolate
(메인 스레드를 말하는 것 같다)에서 실행되며, 큰 작업을 별도의 Isolate
에 할당할 수 있다고 설명한다. https://docs.flutter.dev/perf/isolates
Isolate
는 각각 자체 메모리와 자체 이벤트 루프를 가지고 있으며, 상태를 공유하지 않고 메시지를 통해서만 통신할 수 있다고 한다.
아래 구현 코드에서 차이를 눈으로 확인할 수 있다.
우선 큰 작업을 실행하는 비동기 함수를 작성해준다.
index
변수 3개를 각각 10억번 씩 증가시키는 작업으로, 아주 작은 작업이 30억번 반복되는 것이다.
Future<void> bigTask() async {
int index = 0;
int index2 = 0;
int index3 = 0;
while (index < 1000000000) {
index++;
}
while (index2 < 1000000000) {
index2++;
}
while (index3 < 1000000000) {
index3++;
}
}
Column(
spacing: 10,
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
Text("async", style: TextStyle(fontSize: 30)),
isAsyncTaskDone
? Text("완료", style: TextStyle(fontSize: 40))
: CircularProgressIndicator(),
FilledButton(
onPressed: () async {
setState(() => isAsyncTaskDone = false);
await bigTask(); // 큰 작업 함수
setState(() => isAsyncTaskDone = true); // 완료 후 상태 변경
},
child: Text("실행"),
),
],
)
이제 isolate
를 사용한 코드이다.
큰 작업을 실행하는 비동기 함수를 작성할 때, SendPort
를 이용해서 메시지를 주고받을 수 있도록 해준다. 위에서 설명했듯이 별도의 Isolate
로 분리한 작업은 메시지를 통해서만 통신할 수 있기 때문이다.
작업이 끝나면 true
값을 보내도록 간단하게 구현해준다.
sendPort.send()
함수를 뜯어보면 인자를 Object
로 받기 때문에, 웬만한 값은 모두 넘길 수 있다.
Future<void> isolatedBigTask(SendPort sendPort) async {
int index = 0;
int index2 = 0;
int index3 = 0;
while (index < 1000000000) {
index++;
}
while (index2 < 1000000000) {
index2++;
}
while (index3 < 1000000000) {
index3++;
}
sendPort.send(true);
}
구현 과정은 다음과 같다.
SendPort
를 사용하기 위해 ReceivePort
를 생성한다.Isolate
로 작업 분리 실행 시, Isolate.spawn()
을 사용한다.SendPort
)Isolate
를 제거해야 하기 때문에, 실행 시 await
를 사용하여 인스턴스를 담아둬야 한다.receivePort.listen()
을 사용하여 넘어오는 값을 리스닝한다.Isolate
와 ReceivePort
를 전부 해제하고 닫아준다.// 버튼 부분만 작성함
FilledButton(
onPressed: () async {
ReceivePort receivePort = ReceivePort(); // ReceivePort 생성
setState(() => isIsolateTaskDone = false);
// isolate로 실행
_isolate = await Isolate.spawn(
isolatedBigTask, // 함수
receivePort.sendPort, // 인자
);
// listening
receivePort.listen((message) {
if (!mounted) {
return;
}
// 넘어오는 값이 boolean형일 경우
if (message is bool) {
// true라면
if (message) {
setState(() {
isIsolateTaskDone = true; // 상태 변경
_isolate?.kill(); // isolate 중지
_isolate = null; // isolate 비우기
receivePort.close(); // ReceivePort 닫기
});
}
}
});
},
child: Text("실행"),
)
비동기 실행 모습![]() | isolate 사용 모습![]() |
---|
실행 화면에서 볼 수 있듯이 비동기 함수만을 이용하여 큰 작업을 처리하면 UI가 멈추는 현상이 발생하는 반면, isolate를 사용하여 작업을 분리하면 UI 멈춤 없이 작업을 실행할 수 있다.