このページでは

ブロッキングレシーバー

非GUIスレッドでQSerialPort の同期APIを使用する方法を示します。

「ブロッキング・レシーバー」では、非GUIスレッドでQSerialPort の同期APIを使用して、シリアルインターフェース向けのアプリケーションを作成する方法を示します。

Qt Serial Portのブロッキング受信機

QSerialPort xml-ph-0000@deepl.internal は、主に以下の 2 つのプログラミング手法をサポートしています:

  • 非同期(ノンブロッキング)アプローチ。操作は、制御がQtのイベントループに戻った時点でスケジュールされ、実行されます。操作が完了すると、QSerialPort はシグナルを発行します。たとえば、QSerialPort::write()は直ちに返ります。データがシリアルポートに送信されると、QSerialPort はbytesWritten()を発行します。
  • 同期(ブロッキング)方式。非GUIおよびマルチスレッドアプリケーションでは、waitFor...() 関数(例:QSerialPort::waitForReadyRead())を呼び出すことで、操作が完了するまで呼び出し元のスレッドを一時停止させることができます。

この例では、同期的なアプローチを示しています。ターミナル例では、非同期的なアプローチを説明しています。

この例の目的は、ユーザーインターフェースの応答性を損なうことなく、シリアルプログラミングのコードを簡略化するために使用できるパターンを示すことです。 Qt のブロッキングシリアルプログラミング API を使用すると、コードが簡潔になることがよくありますが、そのブロッキング動作のため、ユーザーインターフェースのフリーズを防ぐために、GUI スレッド以外でのみ使用すべきです。しかし、多くの人が考えていることとは逆に、QThread を使用してスレッドを使用しても、必ずしもアプリケーションに管理しきれないほどの複雑さが加わるわけではありません。

このアプリケーションは「レシーバー」であり、「Blocking Sender」サンプルという送信者アプリケーションとの連携動作を実演するものです。

この「Receiver」アプリケーションは、シリアルポート経由で「Sender」アプリケーションからのリクエストを受信し、それに応答を返します。

まず、シリアルプログラミングのコードを処理する ReceiverThread クラスから見ていきます。

class ReceiverThread : public QThread
{
    Q_OBJECT

public:
    explicit ReceiverThread(QObject *parent = nullptr);
    ~ReceiverThread();

    void startReceiver(const QString &portName, int waitTimeout, const QString &response);

signals:
    void request(const QString &s);
    void error(const QString &s);
    void timeout(const QString &s);

private:
    void run() override;

    QString m_portName;
    QString m_response;
    int m_waitTimeout = 0;
    QMutex m_mutex;
    bool m_quit = false;
};

ReceiverThreadは、Senderからのリクエストを受信するためのAPIを提供するQThread のサブクラスであり、レスポンスの送信やエラーの報告を行うシグナルを備えています。

受信アプリケーションを起動するには、startReceiver() を呼び出す必要があります。このメソッドは、シリアルインターフェースの設定と起動に必要なパラメータを ReceiverThread に渡します。ReceiverThread が送信者からリクエストを受信すると、request() シグナルを発行します。エラーが発生した場合は、error() または timeout() シグナルが発行されます。

startReceiver() はメインの GUI スレッドから呼び出されますが、応答データやその他のパラメータへのアクセスは ReceiverThread のスレッドから行われる点に注意してください。ReceiverThread のデータメンバーは異なるスレッドから並行して読み書きされるため、アクセスを同期させるにはQMutex を使用することをお勧めします。

void ReceiverThread::startReceiver(const QString &portName, int waitTimeout, const QString &response)
{
    const QMutexLocker locker(&m_mutex);
    m_portName = portName;
    m_waitTimeout = waitTimeout;
    m_response = response;
    if (!isRunning())
        start();
}

startReceiver()関数は、シリアルポート名、タイムアウト、および応答データを格納し、QMutexLocker はこれらのデータを保護するためにミューテックスをロックします。その後、スレッドがまだ実行中でない場合は、スレッドを起動します。QWaitCondition::wakeOne()については後で説明します。

void ReceiverThread::run()
{
    bool currentPortNameChanged = false;

    m_mutex.lock();
    QString currentPortName;
    if (currentPortName != m_portName) {
        currentPortName = m_portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = m_waitTimeout;
    QString currentRespone = m_response;
    m_mutex.unlock();

run()関数では、まずミューテックスロックを取得し、メンバデータからシリアルポート名、タイムアウト、および応答データを取得した後、ロックを再び解放します。 いかなる状況においても、startReceiver() メソッドを、これらのデータを取得中のプロセスと同時に呼び出してはなりません。QString は再入可能ですがスレッドセーフではないため、ある起動プロセスからシリアルポート名を読み取り、別の起動プロセスの呼び出しやタイムアウト、応答データを参照することは推奨されません。ReceiverThreadは一度に1つの起動のみを処理できます。

ループに入る前に、run() 関数内でスタック上に構築するQSerialPort オブジェクト:

    QSerialPort serial;

    while (!m_quit) {

これにより、ループの実行中に一度だけオブジェクトを作成することが可能になります。また、このオブジェクトのすべてのメソッドが run() スレッドのコンテキストで実行されることを意味します。

ループ内では、現在の起動におけるシリアルポート名が変更されたかどうかを確認します。変更されている場合は、シリアルポートを再オープンし、再設定します。

        if (currentPortName.isEmpty()) {
            emit error(tr("Port not set"));
            return;
        } else if (currentPortNameChanged) {
            serial.close();
            serial.setPortName(currentPortName);

            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("Can't open %1, error code %2")
                           .arg(m_portName).arg(serial.error()));
                return;
            }
        }

        if (serial.waitForReadyRead(currentWaitTimeout)) {

ループは、リクエストデータの到着を待ち続けます:

            // read request
            QByteArray requestData = serial.readAll();
            while (serial.waitForReadyRead(10))
                requestData += serial.readAll();

警告: ブロッキング方式では、各 read() 呼び出しの前に waitForReadyRead()メソッドを使用 する必要があります。これは、Qt のイベントループの代わりに、すべての I/O ルーチンを処理するためです。

データの読み取り中にエラーが発生した場合、timeout() シグナルが発信されます。

        } else {
            emit timeout(tr("Wait read request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }

読み取りが成功したら、応答の送信を試み、転送の完了を待ちます:

            // write response
            const QByteArray responseData = currentRespone.toUtf8();
            serial.write(responseData);
            if (serial.waitForBytesWritten(m_waitTimeout)) {
                const QString request = QString::fromUtf8(requestData);
                emit this->request(request);

警告: ブロッキング方式では、各 write() 呼び出しの後に waitForBytesWritten()メソッドを使用 する必要があります。これは、Qt イベントループの代わりにすべての I/O ルーチンを処理するためです。

データの書き込み中にエラーが発生した場合、timeout() シグナルが発信されます。

            } else {
                emit timeout(tr("Wait write response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }

書き込みが正常に完了した後、送信元アプリケーションから受信したデータを含む `request()` シグナルが発行されます:

                emit this->request(request);

次に、スレッドはシリアルインターフェースの現在のパラメータの読み取りに切り替えます。パラメータはすでに更新されている可能性があるためです。その後、ループを最初から実行します。

        m_mutex.lock();
        if (currentPortName != m_portName) {
            currentPortName = m_portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = m_waitTimeout;
        currentRespone = m_response;
        m_mutex.unlock();
    }

例の動作

このサンプルは、以下の手順で実行できます。

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.