한국어
Qt
 

Qt 6 Qt 6의 비동기 API

makersweb 2020.10.19 22:13 조회 수 : 1203

이 글에서는 Qt 6에 도입 된 상위 수준의 비동기 API와 변경 사항에 대해 설명한다.

 

Qt의 상위 수준 병렬 처리 API

Qt Concurrent는 저수준 동기화 (뮤텍스 및 잠금과 같은 기본 요소)의 필요성을 제거하고 여러 스레드를 수동으로 관리함으로써 다중 스레드 프로그래밍을 더 쉽게 만든다.

반복 가능한 컨테이너의 병렬 처리를 위해 맵, 필터 및 축소 알고리즘 (함수 프로그래밍에서 더 잘 알려져 있음)을 제공한다. 또한 QFuture, QFutureWatcher 및 QFutureSynchronizer와 같은 클래스가있어 비동기 계산 결과에 액세스하고 모니터링 할 수 있다.

대체적으로 매우 유용하지만 Qt Concurrent 외부에서 QFuture를 사용할 수 없고, 더 간단하고 깔끔한 코드를 위한 다중 계산 결합에 대한 지원 부족, Qt Concurrent API의 유연성 부족 등과 같은 몇 가지 단점이 있다.

Qt 6의 경우 지난 몇 년 동안 받은 피드백을 고려하여 Qt의 멀티 스레드 프로그래밍을보다 즐겁고 재미있게 만들려고 노력했다.

 

QFuture에 연속 연결

멀티 스레드 프로그래밍의 일반적인 시나리오는 비동기 연산을 수행하는 것이다. 연산은 다시 다른 비동기 명령을 호출하고 다른 비동기 연산에 의존하는 데이터를 전달해야 한다.

각 단계에는 이전 단계의 결과가 필요하므로 이전 단계가 완료 될 때까지 (블로킹 또는 폴링하여) 기다렸다가 결과를 사용하거나 "콜백"스타일로 코드를 구성해야 한다.

이러한 옵션 중 어느 것도 이상적이지 않다. 대기중인 리소스를 낭비하거나 유지관리할 수 없는 복잡한 코드로 끝난다. 새로운 단계 또는 로직 (오류 처리 등)을 추가하면 복잡성이 더욱 증가할 것이다.

문제를 더 잘 이해하기 위해 다음의 예를 들어보자. 웹에서 큰 이미지를 다운로드하고 그에 대해 무거운 처리를 하고 결과 이미지를 애플리케이션에 표시하려고 한다고 가정 해 보겠다. 즉, 다음 단계를 수행해야 한다.

  • 네트워크 요청을 하고 모든 데이터가 수신 될 때까지 대기.
  • 원시 데이터에서 이미지를 생성.
  • 이미지 처리.
  • 이미지를 장치로 출력.

 

순차적으로 호출해야 하는 각 단계에 대해 다음 메서드가 있다.

QByteArray download(const QUrl &url);
QImage createImage(const QByteArray &data);
QImage processImage(const QImage &image);
void show(const QImage &image);

QtConcurrent를 사용하여 이러한 작업을 비동기 적으로 수행하고 QFutureWatcher 를 사용하여 진행 상황을 모니터링 할 수 있다.

void loadImage(const QUrl &url) {
    QFuture data = QtConcurrent::run(download, url);
    QFutureWatcher dataWatcher;
    dataWatcher.setFuture(data);
    
    connect(&dataWatcher, &QFutureWatcher ::finished, this, [=] {
        // handle possible errors
        // ...
        QImage image = createImage(data);
        // Process the image
        // ...
        QFuture processedImage = QtConcurrent::run(processImage, image);
        QFutureWatcher<QImage> imageWatcher;
        imageWatcher.setFuture(processedImage);

        connect(&imageWatcher, &QFutureWatcher::finished, this, [=] {
            // handle possible errors
            // ...
            show(processedImage);
        });
    });
}

좋아보이는가? 응용 프로그램 로직은 프로그램 구성 요소를 함께 묶는 데 필요한 표준 코드와 혼합된다. 그리고 더 많은 단계를 추가할수록 더 악화된다는 것을 알고 있다.

QFuture는 QFuture::then() 메서드를 통해 연속 연결 지원을 지원하여 이 문제를 해결하는 데 도움이 된다.

auto future = QtConcurrent::run(download, url)
            .then(createImage)
            .then(processImage)
            .then(show);

확실히 훨씬 좋아 보인다! 그러나 에러처리가 누락된 것을 알 수있다. 다음과 같이 할 수 있다.

auto future = QtConcurrent::run(download, url)
            .then([](QByteArray data) {
                // handle possible errors from the previous step
                // ...
                return createImage(data);
            })    
            .then(...)    
            ...

이 코드는 작동하지만 에러 처리 코드는 여전히 프로그램 로직과 혼합되어 있다. 또한 단계 중 하나가 실패하면 전체 체인을 실행할 이유가 없을 것이다. 이 문제는 QFuture::onFailed() 메서드로 해결할 수 있으며, 이를 통해 각 에러 유형에 대해 특정 에러 처리기를 연결할 수 있다.

auto future = QtConcurrent::run(download, url)
            .then(createImage)
            .then(processImage)
            .then(show)
            .onFailed([](QNetworkReply::NetworkError) {
                // handle network errors
            })
            .onFailed([](ImageProcessingError) {
                // handle image processing errors
            })
            .onFailed([] {
                // handle any other error
            });

.onFailed()를 사용하려면 예외를 활성화해야한다. 예외로 인해 단계가 실패하면 체인이 끊어지고 throw 된 예외 유형과 일치하는 에러 처리기가 호출된다.

.then() 및 onFailed()와 마찬가지로 Future 가 취소될 것을 대비하여 핸들러를 연결하는 .onCanceled()도 있다.

 

시그널에서 QFuture 생성

시그널 또한 미래에 언젠가 사용할 수 있는 무언가를 나타내기 때문에 future와 같이 작업하고 연속, 실패 처리기 등을 첨부 할 수 있는 것이 자연스러워 보인다. void mySignal(int) 시그널이 있는 QObject 기반 클래스 MyObject가 주어지면 이 시그널을 다음과 같은 방법으로 future로 사용할 수 있다.

QFuture intFuture = QtFuture::connect(&object, &MyObject::mySignal);

이제 결과 future에 연속, 실패 또는 취소 처리기를 연결할 수 있다.

결과 미래의 유형은 시그널의 인수 유형과 일치한다. 인수가 없으면 QFuture<void>가 반환되며 인수가 여러 개인 경우 결과는 std::tuple에 저장된다.

이미지 처리 예제의 첫 번째 (즉, 다운로드) 단계로 돌아가서 이것이 실제로 어떻게 유용 할 수 있는지 살펴 보자. 이를 구현하는 방법에는 여러 가지가 있다. QNetworkAccessManager 를 사용하여 네트워크 요청을 보내고 데이터를 가져온다.

QNetworkAccessManager manager;    
...

QByteArray download(const QUrl &url) {        
    QNetworkReply *reply = manager.get(QNetworkRequest(url));
    QObject::connect(reply, &QNetworkReply::finished, [reply] {...});
    
    // wait until we've received all data
    // ...    
    return data;        
}

그러나 잠금을 기다리는 것은 좋지 않다. 이를 제거하고 대신 "QNetworkAccessManager가 데이터를 수신하면 이미지를 생성 한 다음 처리하고 표시"라고 표현하면 더 좋을 것이다. 네트워크 액세스 관리자의 finished() 시그널을 QFuture에 연결하여 이를 수행 할 수 있다.

QNetworkReply *reply = manager.get(QNetworkRequest(url));

auto future = QtFuture::connect(reply, &QNetworkReply::finished)
        .then([reply] {
            return reply->readAll();
        })
        .then(QtFuture::Launch::Async, createImage)
        .then(processImage)
        .then(show)        
        ...

이제 QtConcurrent::run()을 사용하여 데이터를 비동기적으로 다운로드하고 새 스레드에 반환하는 대신, 단순히 QNetworkAccessManager::finished() 시그널에 연결하여 연산 체인을 시작한다는 것을 알 수 있다. 또한 다음 행의 추가 매개 변수에 주목하자.

        .then(QtFuture::Launch::Async, createImage)

기본적으로 .then()에 의해 첨부된 연속성은 부모가 실행해 온 동일한 스레드(우리의 경우 메인 스레드)에서 호출된다. QtConcurrent::run()을 사용하여 체인을 비동기 적으로 시작하지 않았으므로 추가 매개 변수(QtFuture::Launch::Async)를 전달하여 별도의 스레드에서 연속 체인을 시작하고 UI 차단을 방지해야 한다.

번호 제목 글쓴이 날짜 조회 수
공지 Qt프로그래밍(QtQuick) Beginner를 위한 글 읽는 순서 운영자 2019.01.05 85975
179 Qt 응용프로그램에 Web 구성 요소를 표시 with Servo file makersweb 2024.04.27 92
178 Qbs 프로젝트를 정의하기 위해 사용되는 몇가지 중요한 아이템들 file makersweb 2019.10.13 296
177 Qt Creator 에서 GitHub Copilot 사용하기 file makersweb 2024.04.13 322
176 Qt 6.4에 추가될 Qt Quick 3D Physics file makersweb 2022.08.07 344
175 HTTPS URL을 연결할 때 SslHandshakeFailedError 오류 makersweb 2022.07.31 358
174 QtQuick 애플리케이션에 Rive 애니메이션 통합 makersweb 2024.03.31 371
173 Qt Android 앱에 AdMob 배너달기 file makersweb 2021.12.04 392
172 그래픽 소프트웨어에서 디자인 내보내기 (Exporting Designs from Graphics Software) j2doll 2020.12.25 413
171 Binding 타입으로 객체 속성 간 묶기 makersweb 2022.03.04 422
170 Base64로 인코딩된 파일을 복원 makersweb 2023.08.06 460
169 VirtualKeyboard 스타일 커스터 마이징 makersweb 2022.03.13 463
168 Qt Quick Controls 2에 네이티브 데스크탑 스타일 추가 file makersweb 2020.11.23 487
167 앱을 종료할 때 QML 바인딩 오류를 피하는 방법 makersweb 2021.08.08 498
166 안드로이드용 Qt 6.2 makersweb 2021.10.02 505
165 성능 고려 및 제안 사항 makersweb 2022.03.07 508
164 Android 애플리케이션 서명 구성 file makersweb 2023.12.17 511
163 하드디스크 드라이브 여유 공간 계산 file makersweb 2023.01.15 522
162 QRhi 에 대해서 file makersweb 2023.12.29 541
161 Qt Safe Renderer 개요 file makersweb 2022.09.08 545
160 응용프로그램 자동실행 설정 (on Windows) makersweb 2021.05.08 564