[Flutter] 때때로 생성자에서 비동기 요청을 하게되면, 비동기 요청이 실행되기 전에 dispose()가 호출될 수도 있다.

2025. 3. 18. 09:48Programming/Flutter

반응형

문제상황

  ListView에 표시되는 ListViewItem은 필요한 데이터를 요청하기 위해 2~3개의 API를 순차적으로 조회해야했다. 문제는 사용자가 리스트를 스크롤할 경우, 요청하는 API의 수가 기하급수적으로 늘어난다는 것. 이러한 문제를 막기 위해서 스케쥴러를 구현하고, VisibilityDetector를 사용해 ListViewItem이 더 이상 화면에서 사라지면 API 요청을 취소하도록 작성했다. ListViewItem이 dispose를  호출할 때 역시 API 요청을 취소하도록 작성했다. 

  실제로 스크롤을 내리다보면 ListViewItem은 생성자를 호출하는 시점에 API 요청을 스케쥴링을 하고, ListViewItem이 더 이상 렌더링되지 않는 시점에는 스케쥴링한 API 요청을 취소하고 있었다. 하지만, 스크롤을 계속하다보니 스케쥴러에는 수백개의 API 요청이 스케쥴링되고 있었다. 이로 인해서 정작 앞서 스케쥴링된 API를 처리하느라, 화면상에 렌더링된 ListViewItem에서 요청하는 API의 처리는 뒤로 밀리기 시작했다. 어라라, 이게 아닌데.

왜 그럴까?

  스크롤을 계속해서 내리다보면 ListViewItem의 생성자와 Dispose()가 반복해서 호출되는데, 생성자에서 비동기 요청을 하기도 전에 Dispose()가 호출되는 경우가 있었다. 이미 Dispose()가 호출됐다는 얘기는 화면상에서 ListViewItem이 사라졌다는 얘기고, 이 시점에 비동기 요청을 스케쥴링하게 되면 스케쥴 된 작업을 취소할 수 있는 곳이 어디도 없다.

  정확히는 생성자 -> Dispose()의 실행 순서는 보장되겠지만 생성자 내에서 비동기 요청을 스케쥴링하고, Dispose()가 호출되서 스케쥴링된 비동기 작업을 취소하려고 할 때, 스케쥴링 된 비동기 요청이 이벤트 큐에는 존재하지만 태스크 큐에는 없을 수 있기 때문에, 취소를 보장할 수 없는 셈이다.

  여하튼 이미 생성자에서 실행한 비동기 요청이 이벤트 큐에 들어갔고, 스크롤에 의해서 Dispose()가 호출된 시점에는 내가 구현한 스케쥴러를 아무리 뒤져봐도 스케쥴된 작업을 찾을 수 없으니, 약간의 시간이 지난 뒤에 스케쥴러 큐에 비동기 작업이 쌓이는 것을 아무도 막을 수 없는 상황이 펼쳐지고 있었다.

그래서 어떻게 막았는데?

  비동기 요청을 호출할 시점에 ListViewItem의 dispose()가 호출됐는지 여부를 파악하면 되므로, 다음과 같이 _isMounted 플래그를 추가했다. 이후에는 생성자에서 호출한 비동기 요청이 실행될 때, _isMounted값을 확인해서 API 요청을 스케쥴링 하지 않도록 변경하는 걸로 간단하게 해결. 고민했던 것 보다는 쉽게 해결할 수 있는 문제였다. 

class ListViewItem extends ChangeNotifier {
  // ListViewItem이 마운트됐는지 확인하기 위한 _isMounted 플래그 선언
  bool _isMounted = false;
  
  ListViewItem() {
  	// 생성자가 호출됐으므로 마운트됐다고 판단
  	_isMounted = true;
    
    // 생성자에서 여러개의 비동기 요청을 하는 함수를 호출
    callAsyncFunction();
  }
  
  Future<void> callAsyncFunction() async {
    // 비동기 요청을 스케쥴링하기 위해 dispose()가 호출됐는지 여부를 판단
  	if (_isMounted) {
      final result = await scheduleHttpRequestBundles();
    }
  }
  
  @override
  void dispose() {
  	// 이미 dispose()가 호출됐음을 나타내기 위해
    // _isMounted를 false로 설정한다.
    _isMounted = false;
  }
}

 

  Flutter의 비동기 처리 방식은 JavaScript의 비동기 처리 방식과 매우 유사하다. 비동기 호출은 바로 실행되지 않고 마이크로 태스크 큐에 적재되며, 태스크 큐가 비어있으면 실행된다. 비동기 함수의 실행 메커니즘을 숙지하지 않으면, 간단한 문제인데도 고민을 하게 되는 것 같다. 뭐, 모두들 한번씩 데이면서 성장하는 거겠지만. ' ㅇ') 

반응형