Flutter

Isolate

hamiric 2024. 12. 23. 00:52

플러터의 동작방식

플러터는 기본적으로 단일 스레드로 동작하는 애플리케이션.

즉, 플러터는 UI렌더링, 애니메이션, 이벤트 상호작용 등등의 모든 작업이 하나의 스레드에서 이루어진다.

 

플러터에서 일련의 작업들이 동시에 처리되는 것처럼 보이는 이유는 플러터의 이벤트 루프라는 작업방식과, UI를 계속적으로 업데이트 하는 렌더링 방식을 사용하기 때문이다.

 

이벤트 루프 (Event Loop) 와 이벤트 큐 (Event Queue)

이벤트 루프와 이벤트 큐는 플러터에서 비동기 작업을 효율적으로 처리하고, 여러 작업이 동시에 실행되는 것처럼 보이게 만들기 위해 설계된 플러터의 특별한 기능이다. 

 

이벤트가 발생하면, 이벤트 큐에 메시지 형태로 추가되고, 이벤트 큐에 들어간 작업들은 이벤트 루프가 순차적으로 처리할때 까지 대기한다.

이벤트 루프는 계속해서 이벤트 큐를 확인하면서, 대기중인 작업을 처리하게 된다. Flutter는 이벤트 루프가 60FPS로 실행되도록 설계되어 있어, 매 프레임마다 이벤트 큐를 처리하고 UI를 갱신하게 된다.

이벤트 큐와 이벤트 루프
중간에 onTap에 의한 이벤트가 발생한 경우

 

 

위의 방식의 문제점은,

이벤트의 처리가 늦어질 경우, 다음 이벤트 큐에 예정되어있는 UI업데이트 같은 다른 작업이 늦어질 수 있다는 것이다.

( 애니메이션이 작동하고 있는 경우, 애니메이션의 끊김 현상 같은것이 발생한다는 의미 )

 

이때 사용되는 기법이 Isolate이다.

 

 

Isolate란?

Isolate는 대규모 데이터 처리나 병렬 처리 작업에서 성능 향상을 위해 사용되는 병렬 처리 기법.

 

 

멀티 스레드와 Isolate의 차이점

  • 멀티 스레드

하나의 프로세스 안에서 메모리와 자원을 공유하지만, 작업물은 다른 작업공간을 여러개 만드는 것

  • Isolate

하나의 플러터 앱에서, 각자의 메모리 영역을 가지고 독립적으로 실행되는 작업공간을 만드는 것

독자적인 메모리 영역을 가지고 있기 때문에, 데이터를 주고받으려면 통신이라는 메시지 전달이 필요하다.

 

사용법

  • Isolate.spawn

Isolate 기법을 사용하는 가장 일반적인 방식.

새로운 Isolate 작업 공간을 만들어 작업을 실행 할 수 있게 하는 메서드이다.

 

sendPort와 receivePort 를 통해 두 Isolate간의 통신이 이루어진다.

final receivePort = ReceivePort();

// 첫번째 인자 : 새로운 isolate에서 실행할 로직
// 두번째 인자 : 새로운 isolate와 통신을 주고 받기 위한 파라미터
await Isolate.spawn(isolateTask, receivePort.sendPort);

receivePort.listen((message) {
  print("Main received: $message");
  receivePort.close();
});


// 새로운 메모리 공간이 할당된 isolate
// 따라서 main Isolate에서 접근하던 것들은 사용할 수 없다. 
// 일반적으로 rootBundle과 관련된 것들이 해당한다. (ex> assets)
void isolateTask(SendPort mainSendPort) async {
  // ...
  mainSendPort.send("test");
}

 

  • compute

Isolate를 보다 쉽게 구현할 수 있게 만들어진 메서드로, 간단한 Isolate 작업을 할 수 있도록 제공된 메서드이다.

직접 SendPort, ReceivePort 설정하지 않고 사용할 수 있게 SendPort, ReceivePort 설정이 구현되어있는 함수

final usersString = await rootBundle.loadString('assets/30mb.json');
// 첫번째 인자 : 새로운 isolate에서 실행할 로직
// 두번째 인자 : 새로운 isolate에 전달할 데이터
final users = await compute(decodeData, usersString);

Future<List<User>> decodeData(String jsonString) async {
  return List.from(jsonDecode(jsonString)).map((e) {
    return User.fromJson(e);
  }).toList();
}