이 페이지에서

모델과 뷰: 데이터 가져오기를 위해 워커 스레드를 사용하는 리스트 모델

워커 스레드를 사용하여 데이터를 가져오고 반응형 UI를 갖춘 리스트 모델을 구현하는 방법을 보여줍니다.

앨범 아트, 곡명, 가수명, 앨범명이 표시된 노래 목록이 보이는 애플리케이션의 스크린샷

이 예제에서는 ` QAbstractListModel`를 상속하는 사용자 정의 항목 모델을 소개합니다. 이 모델은 별도의 ` QThread`에 있는 워커 객체로부터 데이터를 받아오며, 이 객체는 처리 속도가 느린 데이터 소스에서 데이터를 가져옵니다.

“Threaded Song List” 예제 개요

데이터 소스는 노래 한 곡을 가져올 때마다 100밀리초의 지연 시간을 추가하여 느린 데이터 소스를 시뮬레이션합니다. 즉, 총 3,600곡으로 구성된 전체 목록을 로드하는 데 6분이 소요되므로, 애플리케이션을 실행하는 것이 비실용적입니다. 이 지연은 워커 스레드에 배치된 작업자( QObject )를 사용하여 뷰의 가시 영역에 대한 데이터만 가져옴으로써 완화됩니다.

워커 객체는 큐에 보관할 데이터 가져오기 요청 수에 제한이 있습니다. 이를 통해 사용자가 목록의 일부를 이미 스크롤해 지나간 경우, 목록에서 보이지 않는 부분이 로드될 때까지 기다릴 필요 없이 현재 보이는 부분의 노래 목록 요소만 가져오도록 보장합니다.

이 예제의 핵심은 뷰의 소스 모델에 있습니다. 뷰 자체는 간단한 델리게이트를 가진 수정되지 않은 QML ListView 입니다. 스레드 사용은 모델 데이터 처리 구현 뒤에 숨겨져 있으며, ListView 는 스레드 기반 모델에 적응하기 위해 별도의 사용자 정의가 필요하지 않습니다.

또한 모델에 중점을 두고 있으므로, 모든 플랫폼에서 동일한 UI 동작을 보장하기 위해 Qt Quick Controls 은 유니버설 스타일을 사용하도록 설정되어 있습니다.

import QtQuick
import QtQuick.Controls.Universal

작동 원리

노래 목록 데이터를 제공하는 비즈니스 로직은 모델에 대한 간단한 ID 기반 인터페이스를 제공하는 ` DataStorage ` 클래스로 분리되어 있습니다.

QList<int> idList();
MediaElement item(int id) const;
std::optional<int> currentlyFetchedId() const;

모델이 DataStorage에 데이터를 요청하면, 스토리지에서는 먼저 해당 데이터가 이미 준비되어 있는지 확인합니다. 이미 준비되어 있다면, 비스레드형 모델의 경우와 마찬가지로 데이터가 즉시 반환됩니다. 데이터를 찾을 수 없는 경우, DataStorage는 워커 객체에 dataFetchNeeded() 신호를 발신하고 기존 데이터 목록에 빈 항목을 추가합니다. 빈 항목을 추가함으로써 동일한 목록 항목에 대해 작업자(worker)에게 더 이상 신호가 전송되지 않도록 보장합니다.

if (!m_items.contains(id)) {
    m_items.insert(id, MediaElement{});
    emit dataFetchNeeded(m_idList.indexOf(id));
}
return m_items.value(id);

QueueWorker - 워커 스레드 객체는 수신한 dataFetchNeeded() 신호를 자체적으로 신호를 전송하는 방식으로 처리합니다. 이를 통해 느린 데이터 읽기 작업을 시작하기 전에 QEventQueue에 이미 존재하는 모든 신호를 수신할 수 있습니다.

동적 모델에 이 접근 방식 적용하기

데이터 소스(이 경우 RemoteMedia)에서 항목이 추가, 이동 또는 제거될 수 있는 경우로 솔루션을 확장하려면, DataStorage를 QAbstractItemModel::rowsMoved(), QAbstractItemModel::rowsInserted()에 대응하는 신호로 업데이트하고, ThreadedListModel 내부의 QAbstractItemModel::beginRemoveRows() 및 QAbstractItemModel::endRemoveRows()을 트리거하는 두 개의 신호를 추가해야 합니다.

삽입 및 이동의 경우, ThreadedListModel은 단순히 QAbstractItemModel::beginInsertRows()을 호출한 다음, ID 목록에 새로운 ID를 추가하고 QAbstractItemModel::endInsertRows()을 호출하면 됩니다. ThreadedListModel은 ID 목록의 복사본을 보유하고 ID를 통해 저장소에 접근하므로, 저장소의 시작과 끝을 알릴 필요가 없습니다. 마찬가지로 ThreadedListModel은 QAbstractItemModel::beginMoveRows()를 호출하여 ID 목록 내의 ID를 이동시킨 후, QAbstractItemModel::endMoveRows()를 호출할 수 있습니다.

삭제는 약간 더 복잡한 경우입니다. 뷰는 데이터가 실제로 삭제되기 전에 삭제될 데이터를 요청할 수 있어야 합니다. 따라서 DataStorage는 삭제 경고를 신호하여 Model이 ` QAbstractItemModel::beginRemoveRows()`를 호출하도록 해야 합니다. 이 단계에서 ThreadedListModel은 하나 이상의 ` data() ` 호출을 받을 수 있습니다. 직접 연결된 신호에 대한 호출이 DataStorage로 반환되면, DataStorage는 항목을 제거한 후 모델이 ` QAbstractItemModel::endRemoveRows()`를 호출하도록 트리거하는 또 다른 신호를 통해 모델에 다시 신호를 보낼 수 있습니다.

예제 실행

다음 경로에서 예제를 실행할 수 있습니다:

code.qt.io의 예제 프로젝트

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.