Flutter/Flutter 개발 기록

[flutter] Isolate를 이용한 동시성 제어

딸기케잌🍓 2024. 5. 15. 16:46

요구사항

  1. 앱(.apk파일)을 다운로드하는 도중에 중지 버튼을 눌러 중지시킬 수 있어야 한다.
  2. 다운로드 받으면서 실시간으로 몇 MB까지 다운로드 됐는지 UI를 계속 업데이트 해야 한다.
  3. 현재 앱이 백그라운드로 내려가도, 다운로드는 중지되지 않고 계속 실행되어야 한다.

 

=> Isolate를 이용해서 앱을 별도의 쓰레드에서 다운로드 하도록 하고 중지 버튼 이벤트를 받을 수 있게 해야 한다.

 

Isolate란 무엇인가

Dart 코드는 스레드가 아닌 isolate의 내부에서 실행된다.

각 isolate는 자신의 메모리 힙을 가지고, 다른 isolate에서는 자신의 상태에 접근할 수 없다. (공유하는 메모리가 없다.)

Isolate를 사용하면 Dart 코드가 추가 프로세서 코어를 사용하여 여러가지 독립된 작업을 한 번에 수행할 수 있다.

각 Isolate는 고유한 메모리와 이벤트 루프를 작동시키는 단일 스레드를 가지고 있다.

 

 

Main isolate

기본적으로 Dart 프로그램은 main isolate에서 실행된다.

Main isolate는 프로그램의 실행이 시작되는 스레드이다.

 

 

Isolate 생명 주기

모든 isolate는 main()함수와 같은 Dart 코드를 실행하면서 시작한다. Isolate에서 실행된 Dart 코드가 종료된 후에도 이벤트 처리를 해야 하는 경우 isolate는 계속 유지되고, 이벤트의 처리가 끝난 후 isolate는 종료된다.

 

 

이벤트 처리

Main isolate의 이벤트 큐에는 리페인트 요청, 클릭된 알림 또는 기타 UI이벤트가 포함될 수 있다. 

이벤트 루프는 큐에 진입된 순서대로 이벤트를 처리한다.

 

백그라운드 워커

긴 시간이 소요되는 처리는 다른 isolate로 처리를 위임하는데 이러한 isolate를 백그라운드 워커라고 한다.

워커 isolate는 종료될 때 계산 결과를 메시지로 반환한다.

 

 

Isolate 사이에 다수의 메시지 전송

 

 

 

다운로드 진행상황을 계속 UI에 업데이트 하면서 언제든 다운로드가 중단 될 수 있으려면 일반적인 Dart 코드가 실행되는 Main isolate와 다운로드가 진행되는 Download isolate 사이에서 양방향 통신이 가능해야 했다.

 

 

 

다운로드 진행 상황을 UI에 실시간 업데이트

Download isolate를 만들고 이 때 쓰이는 spawn 함수에 ReceivePort와 쌍을 이루는 SendPort를 전달해 준다.

_receivePort = ReceivePort();
_isolate = await Isolate.spawn(
        downloadFile, [fileUrl, savePath, packageSize, _receivePort?.sendPort]);

이렇게 하면 Download isolate에서 일어나는 특정 결과들을 mainSendPort.send() 함수를 통해 main Isolate의 _receivePort에서 받을 수가 있다.

_receivePort는 Download isolate에서의 다운로드 경과 상황을 계속해서 리스닝한다. 그래서 실시간으로 다운로드 진행 상태를 UI에 업데이트 해줄 수 있다.

_receivePort!.listen((data) async {
      if (data is SendPort) {
        _sendPort = data;
      } else if (data is double) {
        //콜백함수 호출
      } else if (data is String) {
        //콜백함수 호출
        if (data == "Completed") {
          _receivePort!.close();
          _isolate!.kill(priority: Isolate.immediate);
          _isolate = null;

          final res = await InstallPlugin.install(savePath);
          if (res['isSuccess'] == true) {
           //생략..
          } else {
           //생략..
        } else if (data == "Cancelled") {
          _receivePort!.close();
          _isolate!.kill(priority: Isolate.immediate);
          _isolate = null;
        }
      }
    });

 

 

 

다운로드 중간에 중지 처리(Stop Button)

다운로드를 시작하기 전에 미리 Download isolate에서 ReceivePort를 생성하고 이에 대응되는 SendPort를 mainSendPort를 통해 전달한다. 

ReceivePort receivePort = ReceivePort(); //Download Isolate 내부에서 새로운 ReceivePort 생성
mainSendPort.send(receivePort.sendPort)

CancelToken cancelToken = CancelToken(); // CancelToken 생성


receivePort.listen((message) {
    if (message == "cancel") {
        cancelToken.cancel("Cancelled by user");
    }
});

Main isolate에서는 받은 SendPort를 Main isolate에 있는 _sendPort라는 변수에 할당한다.

이제 _sendPort를 통해 전달한 값은 그에 상응하는 ReceivePort가 생성된 Download isolate에서 받을 수 있다.

결국 Main isolate에서 유저가 언제든지 다운로드 Stop button을 누를 때 처리 하기 위함인데 중지 버튼을 탭 한다면 Main isolate에서 다음과 같이 호출한다.

 _sendPort?.send("cancel");

 

 

receivePort에서 리스닝을 하고 있다가 "cancel"이라는 메시지가 들어온다면 Download isolate에서 갖고 있던 CancelToekn.cancel() 함수를 호출하고 download 하던 프로세스를 중지시킬 수 있다.

 

 

 

 

참고 사이트

https://dart-ko.dev/language/concurrency#isolate-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D

 

Dart의 동시성

Isolate를 사용하여 멀티 프로세서 코어에서 병렬 코드를 실행하세요.

dart-ko.dev

 

'Flutter > Flutter 개발 기록' 카테고리의 다른 글

[Flutter] flutter plugin 만들기  (0) 2024.05.26