送信者のブロック
ワーカースレッド内でQSerialPort の同期APIを使用する方法を示します。
「Blocking Sender」では、ワーカースレッド内でQSerialPort の同期APIを使用して、シリアルインターフェース向けのアプリケーションを作成する方法を示します。

QSerialPort 2つのプログラミング手法をサポートしています:
- 非同期(ノンブロッキング)方式。操作はスケジュールされ、制御がQtイベントループに戻った際に実行されます。操作が完了すると、QSerialPort クラスはシグナルを発行します。例えば、write()メソッドは直ちに返ります。データがシリアルポートに送信されると、QSerialPort クラスはbytesWritten()シグナルを発行します。
- 同期(ブロッキング)方式。ヘッドレスおよびマルチスレッドアプリケーションでは、`wait` メソッド(この場合は `waitForReadyRead()`)を呼び出すことで、操作が完了するまで呼び出し元のスレッドを一時停止させることができます。
この例では、同期的な代替手法を示しています。Terminalの例では、非同期的な代替手法を説明しています。
この例の目的は、ユーザーインターフェースの応答性を損なうことなく、シリアルプログラミングのコードを簡略化する方法を実演することです。ブロッキング型のシリアルプログラミング API は、多くの場合、コードを簡潔にできますが、ユーザーインターフェースの応答性を維持するためには、GUI スレッド以外でのみ使用する必要があります。
このアプリケーションは送信側であり、「Blocking Receiver」という受信側アプリケーションと組み合わせて動作する様子を示しています。
送信者アプリケーションは、シリアルポートを介して受信者アプリケーションへの転送要求を開始し、応答を待ちます。
class SenderThread : public QThread
{
Q_OBJECT
public:
explicit SenderThread(QObject *parent = nullptr);
~SenderThread();
void transaction(const QString &portName, int waitTimeout, const QString &request);
signals:
void response(const QString &s);
void error(const QString &s);
void timeout(const QString &s);
private:
void run() override;
QString m_portName;
QString m_request;
int m_waitTimeout = 0;
QMutex m_mutex;
QWaitCondition m_cond;
bool m_quit = false;
};SenderThreadは、受信者へのリクエストをスケジューリングするためのAPIを提供するQThread のサブクラスです。このクラスは、応答やエラー報告のためのシグナルを提供します。transaction()メソッドを呼び出すことで、指定されたリクエストを含む新しい送信トランザクションを開始できます。結果はresponse()シグナルによって返されます。問題が発生した場合は、error()またはtimeout()シグナルが発信されます。
なお、transaction() メソッドはメインスレッドで呼び出されますが、リクエストは SenderThread スレッドで処理されます。SenderThread のデータメンバーは異なるスレッドで並行して読み書きされるため、アクセスを同期させるためにQMutex クラスが使用されています。
void SenderThread::transaction(const QString &portName, int waitTimeout, const QString &request)
{
const QMutexLocker locker(&m_mutex);
m_portName = portName;
m_waitTimeout = waitTimeout;
m_request = request;
if (!isRunning())
start();
else
m_cond.wakeOne();
}transaction() メソッドは、シリアルポート名、タイムアウト、およびリクエストデータを格納します。このデータを保護するために、QMutexLocker を使用してミューテックスをロックできます。スレッドがまだ実行中でない場合は、ここでスレッドを起動できます。wakeOne() メソッドについては後述します。
void SenderThread::run()
{
bool currentPortNameChanged = false;
m_mutex.lock();
QString currentPortName;
if (currentPortName != m_portName) {
currentPortName = m_portName;
currentPortNameChanged = true;
}
int currentWaitTimeout = m_waitTimeout;
QString currentRequest = m_request;
m_mutex.unlock();run() 関数では、まず `QMutex ` オブジェクトをロックし、次にメンバデータを使用してシリアルポート名、タイムアウト、およびリクエストデータを取得します。それが完了したら、QMutex のロックを解除します。
いかなる状況においても、データを取得しているプロセスとtransaction() メソッドを同時に呼び出してはなりません。なお、QString クラスは再入可能ですが、スレッドセーフではありません。したがって、リクエストスレッドでシリアルポート名を読み取り、別のスレッドでタイムアウトやリクエストデータを処理することは推奨されません。 SenderThreadクラスは、一度に1つのリクエストしか処理できません。
QSerialPort オブジェクトは、ループに入る前にrun()メソッド内でスタック上に構築されます。
QSerialPort serial;
if (currentPortName.isEmpty()) {
emit error(tr("No port name specified"));
return;
}
while (!m_quit) {これにより、ループの実行中にオブジェクトを作成することが可能になります。また、これはすべてのオブジェクトメソッドが run() メソッドのスコープ内で実行されることを意味します。
ループ内では、現在のトランザクションのシリアルポート名が変更されたかどうかがチェックされます。変更されている場合、シリアルポートが再オープンされ、再設定されます。
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;
}
}ループは、データの要求、シリアルポートへの書き込みを継続し、すべてのデータが転送されるまで待機します。
// write request
const QByteArray requestData = currentRequest.toUtf8();
serial.write(requestData);
if (serial.waitForBytesWritten(m_waitTimeout)) {警告: ブロッキング転送に関しては 、write メソッドを呼び出すたびに、waitForBytesWritten()メソッドを使用する必要があります。これにより、Qtのイベントループの代わりに、すべてのI/Oルーチンが処理されます。
データ転送中にタイムアウトエラーが発生した場合、timeout() シグナルが発行されます。
} else {
emit timeout(tr("Wait write request timeout %1")
.arg(QTime::currentTime().toString()));
}リクエストが成功した後、応答を待つ待機期間があり、その後、再度読み取りが行われます。
// read response
if (serial.waitForReadyRead(currentWaitTimeout)) {
QByteArray responseData = serial.readAll();
while (serial.waitForReadyRead(10))
responseData += serial.readAll();
const QString response = QString::fromUtf8(responseData);
emit this->response(response);警告: ブロッキング方式の場合 、各 read() 呼び出しの前にwaitForReadyRead() メソッドを使用する必要があります。これにより、Qt イベントループの代わりに、すべての I/O ルーチンが処理されます。
データの受信中にタイムアウトエラーが発生すると、timeout() シグナルが発信されます。
} else {
emit timeout(tr("Wait read response timeout %1")
.arg(QTime::currentTime().toString()));
}トランザクションが正常に完了すると、response() シグナルには受信アプリケーションから受信したデータが含まれます。
emit this->response(response);その後、スレッドは次のトランザクションが現れるまでスリープ状態になります。スレッドは、目覚めた後にメンバを使用して新しいデータを読み取り、ループを最初から実行します。
m_mutex.lock();
m_cond.wait(&m_mutex);
if (currentPortName != m_portName) {
currentPortName = m_portName;
currentPortNameChanged = true;
} else {
currentPortNameChanged = false;
}
currentWaitTimeout = m_waitTimeout;
currentRequest = m_request;
m_mutex.unlock();
}例の動作
以下の手順でサンプルを実行できます:
- Qt Creator
Welcome モードを開き、Examples からこの例を選択します。詳細については、Qt Creator の「チュートリアル:ビルドと実行」を参照してください。
- Qt Extension for Visual Studio Code
Command Palette から「Qt: Open Qt examples 」コマンドを実行し、リストからこのサンプルを選択します。詳細については、Qt Extension for Visual Studio Code の「チュートリアル:ビルドと実行」を参照してください。
「シリアルターミナル」および「ブロッキングレシーバー」も参照してください 。
© 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.