openlang

Isolate.run이 Loader를 통째로 데려갔어요

큰 JSON 디코딩을 Isolate.run으로 메인 isolate 밖에 빼 봤는데, 빨라지기는커녕 보낼 수 없는 객체라는 오류가 났어요. 디코딩 자체는 문제가 없었고 클로저가 문제였어요.

class Loader {
  final updates = StreamController<int>();

  List<Object?> _decode(String text) => jsonDecode(text) as List<Object?>;

  Future<List<Object?>> parse(String text) {
    return Isolate.run(() => _decode(text));
  }
}

_decode가 인스턴스 메서드라서 클로저가 this를 붙잡고, this는 StreamController를 들고 있어요. 클로저가 닿는 건 전부 새 isolate로 보내지는데 컨트롤러는 보낼 수가 없어요. _decode를 static 메서드로 바꾸니 해결됐어요. 아직 모르겠는 건 실행하기 전에 이걸 알아챌 방법이에요. 위 코드에 분석기는 아무 말도 안 하거든요.

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    오류 메시지에 걸린 클래스 이름이 나오는데, 그게 얻을 수 있는 최고의 단서예요. _StreamController나 _AsyncCompleter라고 나오는데 함수가 그걸 안 건드린다면, 클로저 안의 무언가가 그걸 가진 객체를 붙잡고 있는 거예요. 요즘은 Isolate.run에는 최상위 함수나 static 함수만 넘기고 값은 전부 인자로 줘요. 귀찮지만 넘어가는 게 뭔지 한 줄에 다 보여요.

  2. 고유 링크

    저라면 컨트롤러 갱신도 메인 쪽에 남겨 둘 것 같아요. isolate는 디코딩한 리스트만 돌려주고, parse에서 await 다음에 updates로 보내는 거예요. 그러면 백그라운드 함수는 문자열을 받아 리스트를 내는 평범한 함수로 남고, 나중에 디코딩 진행 상황을 알려야 하면 그때 포트를 쓰는 오래 사는 isolate가 필요한지 정하면 돼요.

같은 질문의 다른 갈래

  1. swap! 함수 안의 println

    swap! 문서에는 함수가 여러 번 호출될 수 있으니 부수 효과가 없어야 한다고 적혀 있다. 그런데 실제 코드에는 그 안에서 로그를 찍는 경우가 여전히 있다. 아톰의 값은 어떤 경우에도 맞다. 커밋되는 시도는 하나뿐이기 때문…

  2. 컨텍스트 취소 뒤 결과 전송에서 멈춘 워커들

    단순한 건데 생각보다 오래 붙잡고 있었어요. 워커 여덟 개짜리 풀이 jobs에서 작업을 읽고, 버퍼 없는 results에 결과를 써요. 소비자는 results를 루프로 읽다가 첫 에러가 나면 ctx.Done()으로 돌아가요.…

    답글 8go
  3. fetch 하나가 실패할 때의 asyncio.TaskGroup

    언어끼리 동시성을 비교할 때는 보통 문법부터 봐요. 저는 뭔가 실패했을 때 계산에 무엇이 필요한지부터 보고 싶어요. 작은 Python 애플리케이션이 측정값과 가중치를 동시에 가져와서 점수 하나로 합쳐요. 그런데 fetch 하…