본문 바로가기
Flutter

Flutter에서 long running isolate 사용하기

by 붕어사랑 티스토리 2023. 10. 25.
반응형

https://dart.dev/language/concurrency

 

Concurrency in Dart

Use isolates to enable parallel code execution on multiple processor cores.

dart.dev

 

 

 

1. 단일 작업을 위한 Isolate

기본적으로 Isolate.run()을 사용하면 Isolate를 하나 만들어준뒤, 전달해준 함수를 실행해주고, 함수가 값을 리턴하면 result값을 받아와준다. 작업이 종료되면 Isolate를 종료한다.

 

이때 한가지 기억할건, 결과값은 메모리로 전달된다. 데이터를 카피하지 않음

void main() async {
  final result = await Isolate.run(_MyFuction);

  // 데이터 사용.
  print('result is $result');
}

 

혹은 compute를 사용한다. 공식문서에서 아래와 같이 Isolate.run과 compute의 차이를 설명하는데.. 사실상 똑같은거라 생각하면 될 듯

 

On native platforms await compute(fun, message) is equivalent to await Isolate.run(() => fun(message))

 

 

 

2. 오랫동안 실행되는 Isolate 만들기

Isolate.spawn()Isolate.exit()를 사용하면 된다. 사실상 위의 run함수는 spawn과 exit 그리고 예외처리등을 묶어서 간편하게 해 주는 함수이다.

 

앱개발자라면 백그라운드 스레드를 만들고 거기에 무거운 작업을 올리고 싶을 것이다. 그러면 다음과 같은 작업을 따르면 된다.

 

 

 

  • Isolate.spawn을 이용하여 Isolate를 생성한다
  • Main Isolate에서 ReceivePort()를 생성한다.
  • ReceivePort()는 sendPort프로퍼티를 가지고 있으며, 사실상 상대방에게 나의 주소를 알려주는 프로퍼티이다.
  • 백그라운드에서 돌 Isolate에서도 ReceivePort()를 생성한 뒤 sendPort를 Main Isolate에 전달해준다
  • 각자 전달받은 Port를 이용하여 데이터 통신을 진행한다.

 

 

아래는 공식문서에서 제공하는 예제이다. 한번 분석해보자

 

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';

import 'package:async/async.dart';

const filenames = [
  'json_01.json',
  'json_02.json',
  'json_03.json',
];

void main() async {
  await for (final jsonData in _sendAndReceive(filenames)) {
    print('Received JSON with ${jsonData.length} keys');
  }
}

Stream<Map<String, dynamic>> _sendAndReceive(List<String> filenames) async* {
  final p = ReceivePort();
  
  // Isolate를 만들어준다. 이때 Main Isolate에서 포트를 만든 뒤 sendPort를 전달해준다
  await Isolate.spawn(_readAndParseJsonService, p.sendPort);

  // 포트로부터 백그라운드에서 받을 데이터 스트림을 획득한다.
  // 이 스트림은 Isolate간의 데이터 통로이다.
  final events = StreamQueue<dynamic>(p);

  // 백그라운드에서 첫번째로 전달된 데이터는 포트이며, 백그라운드로부터 포트번호를 받아온다.
  // next를 이용하면 스트림큐에서 데이터를 읽어올 수 있다.
  SendPort sendPort = await events.next;

  // 백그라운드의 포트를 알았으니, 포트를 이용하여 데이터를 백그라운드에 전달하고, 결과값을 기다린다.
  for (var filename in filenames) {
    sendPort.send(filename);
    Map<String, dynamic> message = await events.next;
    yield message;
  }

  // 백그라운드에서 null을 받으면 Isolate를 종료하도록 처리했다.
  // 즉 아래 코드는 통신을 종료하겠다는 의미
  sendPort.send(null);

  await events.cancel();
}

Future<void> _readAndParseJsonService(SendPort p) async {
  print('Spawned isolate started.');
  
  // 백그라운드 작업의 포트를 만들어준다.
  final commandPort = ReceivePort();
  // 메인 Isolate로 부터 전달받은 포트를 이용하여 
  p.send(commandPort.sendPort);

  // 메인 포트로부터 데이터가 오길 기다린 뒤, 데이터가 오면 작업을 수행한다
  // null이 오면 for문을 종료하고 Isolate를 종료한다.
  await for (final message in commandPort) {
    if (message is String) {
      final contents = await File(message).readAsString();

      p.send(jsonDecode(contents));
    } else if (message == null) {
      break;
    }
  }

  print('Spawned isolate finished.');
  Isolate.exit();
}

 

 

 

위 코드에서 메인 Isolate에서  ReceivePort를 StreamQueue로 변환하는 작업이 있는데, 데이터를 백그라운드 처럼 받기만 할 경우 StreamQueue로 변환은 필요 없고, await for 만 사용해도 된다.

 

 

 

반응형

댓글