728x90
반응형

QCoro로 활용하는 Qt 비동기 프로그래밍

  • QCoroC++20코루틴(coroutine) 기능을 Qt 프레임워크 에 자연스럽게 통합하여, 비동기 코드를 훨씬 간결하고 직관적으로 작성할 수 있도록 도와주는 라이브러리입니다.

프로젝트 주소

지원 환경

  • 컴파일러 C++20 이상
    • GCC: 10 이상
    • Clang: 13 이상
    • MSVC: 2019 version 16.10 이상
  • Qt 6.x (일부 Qt 5.x도 지원)
  • Linux, macOS, Windows 호환


프로젝트 개요

  • 전통적으로 Qt에서는 비동기 작업을 QFuture, QPromise, signal-slot, 또는 callback 메커니즘을 사용해 처리해왔습니다.
  • 하지만 이런 방식은 코드 가독성이 떨어지고, 복잡한 로직에서는 콜백 지옥(callback hell)으로 이어지기 쉽습니다.
  • QCoro 는 이러한 문제를 해결하기 위해 Qt의 비동기 API를 코루틴 스타일로 변환하여 co_await를 통해 간결하고 직관적인 비동기 흐름을 구현할 수 있게 해줍니다.


주요 기능

  • Qt 네이티브 지원

    • QIODevice, QProcess, QNetworkReply, QTimer 등 주요 Qt 클래스에 대한 비동기 co_await 지원을 제공합니다.
  • 시그널 대기

    • co_await를 통해 특정 시그널이 발생할 때까지 대기하는 기능을 제공합니다.
  • 커스텀 타입 확장

    • 자신만의 타입에 대해서도 코루틴 awaitable 기능을 확장할 수 있습니다.
  • 간결한 문법

    • 복잡한 상태 관리나 연결 코드를 작성하지 않고, 마치 동기 코드처럼 자연스럽게 작성할 수 있습니다.
  • 기본 구조

      QCoro::Task<> userDefinedTask(...) { // QCoro 태스크 생성
       ... 
       co_await doJob(); // QCoro 대기 (doJob이 종료될 때까지 대기)
       ...
       co_return; // QCoro 태스크 리턴
     }
    


QNetworkAccessManager 비동기 요청 예제

  • cpp

      #include <QCoreApplication>
      #include <QNetworkAccessManager>
      #include <QNetworkRequest>
      #include <QNetworkReply>
      #include <QUrl>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/network.h>
      #include <coroutine>
      
      QCoro::Task<> fetchData(QNetworkAccessManager &manager) {
          QUrl url{"https://postman-echo.com/get?foo1=bar1&foo2=bar2"};
          QNetworkRequest request(url);
      
          auto reply = co_await manager.get(request); // GET URL 결과 대기
     	 
          QByteArray data = reply->readAll();
          qDebug() << "응답 내용:" << data;
      
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          QNetworkAccessManager manager;
          fetchData(manager); // QCoro 태스크 시작
          return app.exec();
      }
    

  • 분석
    • co_await manager.get() 호출로 네트워크 응답 완료 시점까지 suspension.
    • 기존의 connectQNetworkReply 처리를 간결화.
    • 이벤트 루프가 동작 중인 상태에서 코루틴 실행 가능.


QProcess 비동기 실행 예제

  • cpp

      #include <QCoreApplication>
      #include <QProcess>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/process.h>
      #include <coroutine>
      
      QCoro::Task<> runProcess() {
          QProcess process;
          process.start("echo", {"Hello from QProcess!"}); // 외부 프로그램 echo 실행
     	 
          const auto result = co_await process; // process 대기
     	 
          qDebug() << "프로세스 종료 코드:" << result.exitCode;
          qDebug() << "표준 출력:" << process.readAllStandardOutput();
     	 
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          runProcess(); // QCoro 태스트 시작
          return app.exec();
      }
    

  • 분석
    • process 종료 대기를 co_await로 처리.
    • 종료 코드와 표준 출력 내용을 바로 가져옴.


QTimer 비동기 대기 예제

  • cpp

      #include <QCoreApplication>
      #include <QTimer>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/timer.h>
      #include <coroutine>
      
      QCoro::Task<> waitWithTimer() {
          QTimer timer;
          timer.setInterval(2000); // 2000 밀리초 = 2초
          timer.setSingleShot(true); // 한번만 실행
     	 
          co_await timer; // timer 대기
     	 
          qDebug() << "2초가 경과했습니다.";
     	 
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          waitWithTimer(); // QCoro 태스트 시작
          return app.exec();
      }
    

  • 분석
    • 타이머 만료를 co_await로 기다림.
    • 기존 connect 없이 자연스럽게 코루틴 흐름으로 전환.


QFile 비동기 파일 쓰기/읽기 예제

  • cpp

      #include <QCoreApplication>
      #include <QFile>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/file.h>
      #include <coroutine>
      
      QCoro::Task<> writeAndReadFile() {
          QFile file("example.txt");
      
          if (co_await file.open(QIODevice::WriteOnly)) { // 파일 열기 대기
              co_await file.write("QCoro 파일 비동기 쓰기 테스트"); // 파일 쓰기 대기
              file.close();
          }
      
          if (co_await file.open(QIODevice::ReadOnly)) { // 파일 열기 대기
              const QByteArray content = co_await file.readAll(); // 파일 읽기 대기
              qDebug() << "읽은 파일 내용:" << content;
              file.close();
          }
      
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          writeAndReadFile(); // QCoro 태스크 시작
          return app.exec();
      }
    

  • 분석
    • 파일 열기, 쓰기, 읽기를 모두 co_await로 간결화.
    • 코루틴 내에서 직관적인 비동기 입출력 구현 가능.


QTcpSocket 비동기 연결 및 통신 예제

  • cpp

      #include <QCoreApplication>
      #include <QTcpSocket>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/socket.h>
      #include <coroutine>
      
      QCoro::Task<> connectToServer() {
          QTcpSocket socket;
     	 
          co_await socket.connectToHost("example.com", 80); // 연결 대기
      
          if (socket.state() == QAbstractSocket::ConnectedState) {
              qDebug() << "서버에 연결되었습니다.";
              socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
      
              while (co_await socket.waitForReadyRead()) { // 읽기 준비 대기
                  QByteArray response = socket.readAll();
                  qDebug() << "응답 데이터:" << response;
              }
          } else {
              qDebug() << "서버 연결 실패";
          }
      
          co_return; // QCoro 태스크 리턴 
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          connectToServer(); // QCoro 태스크 시작
          return app.exec();
      }
    

  • 분석
    • 비동기 연결과 데이터 수신 대기를 모두 co_await로 처리.
    • 콜백 및 이벤트 신호 연결 필요 없이 비동기 TCP 작업 가능.


QDialog 비동기 실행 예제

  • cpp

      #include <QApplication>
      #include <QDialog>
      #include <QPushButton>
      #include <QVBoxLayout>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/dialog.h>
      #include <coroutine>
      
      QCoro::Task<> showDialog() {
          QDialog dialog;
          dialog.setWindowTitle("비동기 QDialog 테스트");
          auto layout = new QVBoxLayout(&dialog);
          auto button = new QPushButton("닫기");
          layout->addWidget(button);
      
          QObject::connect(button, &QPushButton::clicked, &dialog, &QDialog::accept);
      
          int result = co_await dialog; // dialog 대기 (modal 다이얼로그와 비슷한 효과)
     	 
          qDebug() << "다이얼로그 종료, 반환 코드:" << result;
      
          co_return; // QCoro 태스크 리턴 
      }
      
      int main(int argc, char *argv[]) {
          QApplication app(argc, argv);
          showDialog(); // QCoro 태스크 시작 
          return app.exec();
      }
    

  • 분석
    • 다이얼로그가 닫힐 때까지 비동기 대기.
    • 기존 exec() 대신 co_await를 사용하여 UI 흐름 제어 가능.


QEventLoop 비동기 대기 예제

  • cpp

      #include <QCoreApplication>
      #include <QTimer>
      #include <QEventLoop>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/eventloop.h>
      #include <coroutine>
      
      QCoro::Task<> eventLoopExample() {
          qDebug() << "3초 후에 메시지를 출력합니다.";
      
          QEventLoop loop;
          QTimer::singleShot(3000, &loop, &QEventLoop::quit); // 3000 밀리초 후 종료(quit) 이벤트 생성
      	
          co_await loop; // loop 대기
      
          qDebug() << "3초 경과!";
      	
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          eventLoopExample(); // QCoro 태스크 시작 
          return app.exec();
      }
    

  • 분석
    • 특정 이벤트 발생 전까지 QEventLoop 안에서 대기.
    • 기존 이벤트 루프 흐름을 자연스럽게 co_await로 처리.


QFuture (QtConcurrent) 비동기 실행 예제

  • cpp

      #include <QCoreApplication>
      #include <QtConcurrent>
      #include <QDebug>
      #include <qcoro/qcoro.h>
      #include <qcoro/future.h>
      #include <coroutine>
      
      QCoro::Task<> runConcurrentTask() {
      
          auto future = QtConcurrent::run([]() -> int {
              QThread::sleep(2); 
              return 42;
          });
      
          int result = co_await future; // future 대기
     	 
          qDebug() << "QtConcurrent 결과:" << result;
      
          co_return; // QCoro 태스크 리턴
      }
      
      int main(int argc, char *argv[]) {
          QCoreApplication app(argc, argv);
          runConcurrentTask(); // QCoro 태스크 시작 
          return app.exec();
      }
    

  • 분석
    • QtConcurrent 작업의 결과를 자연스럽게 코루틴으로 대기.
    • 별도 스레드 작업의 결과를 간단히 받아오는 구조.


정리

  • QCoroQt의 다양한 비동기 작업을 C++20 코루틴으로 감쌀 수 있게 하여, 코드의 가독성과 유지보수성을 크게 높여줍니다.
  • 복잡한 콜백이나 신호-슬롯 연결 없이도 co_await로 자연스럽게 이벤트 흐름을 제어할 수 있다는 점이 큰 장점입니다.
  • Qt를 사용하며 비동기 처리를 자주 다루는 경우, QCoro는 필수적인 도구가 될 수 있습니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형

+ Recent posts