티스토리 뷰

반응형

Isolate 가 뭐길래?

Dart는 기본적으로 싱글 스레드를 기반으로 합니다.

하지만 스트리밍, 백그라운드 작업을 지원합니다. 그 이유는 Dart의 모든 코드는 Isolate 기반으로 동작을 하기 떄문인데요

싱글스레드가 격리된 메모리 공간에서 Isolate를 사용해 _이벤트 루프_를 실행시켜 멀티스레드 처럼 동작시킬 수 있습니다.

/// Event Roop는 Event Queue에 담겨 주어진 작업을 순서대로 처리해줍니다.

이러한 Isolate를 코드에서 적절히 사용해주면 개인적으로 Flutter의 큰 단점중 하나라고 생각되는 화면의 버벅거림을 줄여 UX를 좋게 만들어 줄 수 있습니다.

그렇다면 Isolate가 Thread 인가요??

정확히는 아이솔레이트는 스레드를 감싸는 wrapper입니다! 스레드는 다른 스레드와 메모리를 공유할 수 있어서 개발할때 여러모로 편하다고 하지만 여러 스레드에서 하나의 변수를 참조하다보면 오류가 생기기 마련입니다. 따라서 아이솔레이트에서는 메모리를 공유할 수 없습니다. 대신에 값이담긴 메세지를 주고받으며 대화를 하는데 그건 아래 코드에서 알아보겠습니다!

Isolate를 이해해보자!

  String? loadedData;
  late Isolate _isolate;
  final _receivePort = ReceivePort();

  @override
  void initState() {
    super.initState();
    _receivePort.listen((message) {
      setState(() {
        loadedData = message.toString();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return MainFrame(
      route: FeatureEnum.isolate,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Builder(builder: (_) {
            if(loadedData == null){
              return const SizedBox();
            }
            if(loadedData!.isEmpty){
              return const CircularProgressIndicator();
            }
            return Text(loadedData!);
          },),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapMethodCall, child: const Text('Call Method')),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapMethodCallWithIsolate, child: const Text('Call Method With Isolate')),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapCancelIsolate, child: const Text('Cancel Isolate')),
        ],
      ),
    );
  }

위와 같은 화면이 있습니다.

loadedData 라는 String 값이 null 이면 빈화면, 빈값이면 로딩, 유의미한 값이면 Text를 보여주는 위젯입니다.

아래에는 ElevatedButton 이 세개가 있네요! 각각 하는 일을 알아봅시다.

/// 아이솔레이트 없이 sleep을 호출함 -> 화면 멈춤
  Future<void> onTapMethodCall() async {
    setState(() {
      loadedData = '';
    });
    sleep(const Duration(milliseconds: 5000));
    setState(() {
      loadedData = 'just sleep over';
    });
  }

  /// 아이솔레이트를 통해 별도 메모리를 할당시켜 UI에 영향을 주지 않고 로직 실행
  Future<void> onTapMethodCallWithIsolate() async {
    setState(() {
      loadedData = '';
    });

    _isolate = await Isolate.spawn<SendPort>(
        sleepApp, _receivePort.sendPort);
  }

  /// 작업 중단
  void onTapCancelIsolate(){
    _isolate.kill(priority: Isolate.immediate);

    setState(() {
      loadedData = 'Task canceled';
    });
  }

우선 첫번째 버튼의 옵션인 onTapMethodCall 입니다.
코드를 보면 앱을 로딩상태로 만들어주고 5초간 대기 후 로딩을 풀어주는 식으로 되어있습니다.

그리고 두번째 버튼은 onTapMethodCallWithIsolate 함수를 호출하는데 이는 위 함수와 똑같이 앱을 로딩상태로 만들어주고 아래와 같은 sleepApp 함수를 통해 5초간 대기시킨 후 로딩을 풀어주게 되어있습니다.

세번째 함수는 아이솔레이트가 돌고있는 상태에서 kill 함수를 통해 작업을 중단시키는 함수입니다.

void sleepApp(SendPort sendPort) {
  sleep(const Duration(seconds: 5));
  sendPort.send("Completion");
}

얼핏보면 onTapMethodCall 함수와 onTapMethodCallWithIsolate는 하는 역할이 비슷하게 보입니다.

차이점이라고는 Isolate.spawn 함수를 통해 sleepApp 함수를 실행시켰다는 점인데요 실제 동작이 어떻게 이루어지는지 한번 보겠습니다.

결과는 어떻게 되었나?

첫번째 버튼을 누른 결과입니다.

앱이 멈춰버리며 5초가 지나면 'just sleep over' 라는 메세지가 나타납니다.

원하는 결과는 나왔지만 그 과정에 ui 에 영향을 주고말았습니다. 그렇다면 Isolate를 사용한 작업은 어떨까요?

위와같이 정상적으로 로딩이 실행되고 5초가 지나 'Completion' 이라는 문구가 나타나게 됩니다.

Isolate 사용법

위 코드에서는

_isolate = await Isolate.spawn<SendPort>(
    sleepApp, _receivePort.sendPort);

이런식으로 Isolate.spawn() 함수를 사용하는데 이 함수는 인자로 사용할 함수와 파라미터를 전달받아 실행시켜 줍니다.

이때 전달받은 함수는 격리된 메모리 공간에서 실행되며, 보통 시간이 오래걸리는 작업을 수행할 때 사용합니다.

 

_receivePort는 이미 initState에서

@override
void initState() {
  super.initState();
  _receivePort.listen((message) {
    setState(() {
      loadedData = message.toString();
    });
  });
}

이렇게 리스너를 등록해준 상태이고, _receivePort.sendPort를 인자로 전달해줘서 등록해준 리스너에 응답을 줄 수 있게끔 되어있습니다.

 

void sleepApp(SendPort sendPort) {
  sleep(const Duration(seconds: 5));
  sendPort.send("isolate over!");
}

위 sleepApp 함수는 Isolate.spawn() 함수로 호출해준 함수인데 sendPort.send() 함수를 통해 원하는 메세지를 전달할 수 있습니다.

여기서 주의해야 할 점은 Isolate.spawn()에 등록해줄 함수는 Top-Level의 함수이거나 static 함수여야 한다는 점입니다.

 

마지막으로

_isolate.kill(priority: Isolate.immediate);

작업을모두 마친 상태라면 위처럼 kill을 호출해줘서 메모리에서 지워줘야 합니다. 

 

주의할점

아이솔레이트는 격리된 메모리 공간에서 새로운 작업을 합니다. 따라서 싱글톤과 같이 메모리 공간을 이미 잡고있는 객체를 아이솔레이트 내에서 새로 선언하게되면 기존에 사용하던 객체를 가져오는것이 아니라 새로운 객체를 만들어 사용하게 되기 때문에 사용에 주의를 요할 필요가 있습니다.

 

 

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함