본문 바로가기
프로그래밍/Dart

Dart isolate에서 탭 선택이 꼬인 날 — 늦은 응답을 거르는 방법

by iwbap 2026. 10. 17.
728x90
반응형

Dart isolate에서 백그라운드 작업 결과를 받아 화면을 갱신하다가 탭 선택이 꼬이는 문제를 겪었다. 이전 요청의 응답이 늦게 도착해 최신 상태를 덮어쓰고 있었다. 결국 요청 번호로 오래된 결과를 걸러내고, UI 상태 변경의 경계를 정리했다.

늦게 도착한 isolate 응답을 요청 번호로 걸러내는 과정을 상징하는 대표 이미지

처음에는 결과만 받으면 되는 줄 알았다

처음에는 백그라운드 isolate의 결과를 받아 메인 isolate에서 상태를 바로 갱신하면 될 줄 알았다. 간단한 샘플에서는 화면도 바로 갱신됐고, 별문제가 없어 보였다.

문제는 작업이 길어지고 요청이 겹치면서 나타났다. 샘플에서 잘 돌아간다고 실제 화면에서도 응답 순서가 보장되는 건 아니었다.

탭을 빠르게 오가자 이전 응답이 덮어썼다

탭을 빠르게 오갈 때 이전 요청 결과가 늦게 도착하면서 최신 선택값을 덮어썼다. 로딩이 끝났는데도 다른 항목이 선택된 상태로 보이는 현상이 생겼다.

탭 A 요청의 응답이 탭 B 응답보다 늦게 도착해 최신 선택을 덮어쓰는 타임라인 도식

처음에는 화면 갱신 과정만 신경 썼지만, 실제로는 어떤 요청의 결과를 반영해야 하는지가 문제였다. isolate 자체가 잘못된 게 아니라 비동기 응답을 받아들이는 방식에 빈틈이 있었다.

sequence 번호로 오래된 응답을 걸러냈다

그래서 요청마다 sequence 번호를 붙였다. 응답이 돌아오면 현재 요청 번호와 비교하고, 일치하는 결과만 UI에 반영하도록 했다.

최신 요청 번호와 일치하는 응답만 setState로 반영하고 이전 번호 응답은 버리는 흐름 도식

핵심은 최신 요청의 결과만 반영하는 것이었다. 아래는 그 흐름을 보여주는 Dart 예시 코드다. 함수명과 상태 변수는 설명을 위해 단순화했다.

int latestSequence = 0;

Future<void> loadTab(String tabId) async {
  final sequence = ++latestSequence;
  final result = await requestFromIsolate(tabId);
  if (sequence != latestSequence) return;

  setState(() {
    currentData = result;
  });
}

이렇게 하니까 늦게 온 응답이 최신 상태를 덮어쓰지 않았다. 빠르게 화면을 바꿔도 체감상 상태가 튀는 현상은 사라졌다.

다만 번호 비교는 오래된 결과를 버릴 뿐, 이미 실행 중인 작업 자체를 취소하지는 않는다. 취소 가능한 작업을 별도로 처리하는 부분은 아직 남은 과제였다.

isolate 통신과 UI 상태 변경을 분리했다

isolate 통신은 한 곳에서 받고, UI 데이터는 메인 isolate의 이벤트 큐를 통해 처리하도록 경계를 정리했다. 어떤 응답이 상태를 변경할 수 있는지 명확히 하는 데 집중했다.

백그라운드 isolate 응답을 한 곳에서 받아 번호를 확인한 뒤 메인 isolate 상태와 UI로 전달하는 경계 도식

디버그 화면에는 요청 번호와 처리 시간을 함께 찍었다. 재현하기 어려운 타이밍 버그일수록 요청과 응답의 순서를 눈으로 확인할 수 있어야 했다.

이번에는 로그를 통해 순서를 확인했지만, 개선 전후의 오류 발생률이나 응답 지연 시간을 별도로 측정하지는 않았다. 다음에는 이런 값도 함께 기록해 비교할 생각이다.

결국 중요한 건 상태의 주인이었다

이날은 위젯을 예쁘게 만드는 것보다 비동기 경계를 확실히 하는 날이었다. isolate 간 통신이 메시지 기반이어도 상태의 주인은 한 곳에 두기로 했다.

이번 작업에서 배운 점은 세 가지였다.

  • 비동기 작업의 완료 순서가 요청 순서와 같다고 가정하지 않는다.
  • 요청 번호로 오래된 결과를 걸러내되, 실제 작업 취소는 별도로 처리한다.
  • UI 상태 변경 책임을 명확히 하고, 요청과 응답의 순서를 로그로 확인한다.

완벽하게 끝난 건 아니다. 다음에는 남아 있는 작업 취소 처리를 보완하고, 응답 지연과 상태 변경 결과도 측정해 볼 생각이다.

728x90
반응형