Qt 4.8 で Thread を中断する

Posted Mon Apr 25 2016

先日,会社で Qt を使ってツールを作っていたのですが,スレッドの中断処理を実装する必要がありました.Qt 5.2 から QThread にはスレッドの中断機構が組み込まれているのですが,4系にはなかったため実装しました.

最初 boost の interruption_point を使って中断しようと思ったのですが,Qt とは相性が悪い?のか上手くいかなかったため,素直に QThread で実装しました.

中断処理部分を抽出し,stopwatch を作ってみました.完全なソースコードは github にあります.ビルドシステムとして gyp を使っています.

スレッドの中断処理

中断処理の要となるのは下記の2つの関数です.Qt 5.2 以降には頭文字を小文字にした同等の関数が組み込みであります.

  • IsInterruptionRequested

    • スレッドの中断リクエストがされたか
  • RequestInterrupttion

    • スレッドの中断をリクエストする

ストップウォッチクラスではスタートボタンが押下されたあと,1msec間隔で経過時間を通知します.ループ内では中断リクエストがされているかをチェックし,中断リクエストがなければ継続します.

#include "stopwatch.h"

#include <QThread>

class Sleeper : public QThread {
 public:
  static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
};

void Stopwatch::Start() {
  ClearInterruptionRequest();
  elapsed_timer_ = new QElapsedTimer;
  elapsed_time_ = 0;
  elapsed_timer_->start();
  while (!IsInterruptionRequested()) {
    Sleeper::msleep(1);
    elapsed_time_ = elapsed_timer_->elapsed();
    emit Elapsed(elapsed_time_);
  }
  delete elapsed_timer_;
}

void Stopwatch::Restart() {
  ClearInterruptionRequest();
  elapsed_timer_ = new QElapsedTimer;
  elapsed_timer_->start();
  qint64 tmp;
  while (!IsInterruptionRequested()) {
    Sleeper::msleep(1);
    tmp = elapsed_timer_->elapsed() + elapsed_time_;
    emit Elapsed(tmp);
  }
  elapsed_time_ = tmp;
  delete elapsed_timer_;
}

void Stopwatch::RequestInterruption() {
  mutex_.lock();
  interruption_requested_ = true;
  mutex_.unlock();
}

void Stopwatch::ClearInterruptionRequest() {
  mutex_.lock();
  interruption_requested_ = false;
  mutex_.unlock();
}

bool Stopwatch::IsInterruptionRequested() {
  bool flag;
  mutex_.lock();
  flag = interruption_requested_;
  mutex_.unlock();
  return flag;
}

ストップボタンが押された場合,中断要求シグナルを発行します.

    emit RequestInterrupttion();
    stopwatch_thread_.quit();

上記のプログラムでは connect 時のオプションとして Qt::DirectConnection を指定する必要があります.このオプションを指定しないと中断処理が行われません.ストップウォッチのループの中で Qt のイベント処理を行うように変更すればオプション指定しなくても大丈夫だと思います.

  connect(this, SIGNAL(RequestInterruption()), stopwatch_,
          SLOT(RequestInterruption()), Qt::DirectConnection);