QThreadの適切な使用

Qtを使用した最近のプロジェクトでは、QThreadクラスを処理する必要がありました。 その結果、他のプロジェクトで使用する「正しい」QThreadテクノロジーに到達しました。



挑戦する



数百から数千のかなり大きな数のファイルでディレクトリを監視するサービスがあります。 ファイルの内容の分析に基づいて、レポートが作成されます(異なるタイプの最大5つのレポート)。 ファイルの内容が変更された場合、またはファイルの数が変更された場合、レポートは中断され、最初からやり直されます。 レポート作成機能はReportBuilder



クラスに実装されています。 レポートの各タイプは、 ReportBuilder



から継承した独自のクラスを使用します。 並列スレッドでレポートを作成することをお勧めします。



Qtドキュメントの例:間違っています



ドキュメントとQt サンプルを読むことから始めました。 すべての例で、スレッドはQThreadクラスを継承し、 run()



メソッドをオーバーライドすることにより作成されます。

 class MyThread : public QThread { Q_OBJECT protected: void run(); }; void MyThread::run() { ... }
      
      







開発者のQt Bradley T.Hughesが、別のスレッドでクラスコードを実行するためだけにQThreadを継承することは根本的に間違った考えであると主張している途中で、私は投稿を読みました。

「QThreadは、スレッドで実行したいコードを置く場所としてではなく、オペレーティングシステムスレッドへのインターフェイスまたはコントロールポイントとして使用することを目的として設計されています。 基本クラスの機能を拡張または特殊化したいため、オブジェクト指向プログラマーはサブクラスです。 QThreadをサブクラス化するために考えられる唯一の正当な理由は、QThreadにない機能を追加することです。たとえば、スレッドのスタックとして使用するメモリへのポインターを提供したり、リアルタイムインターフェイス/サポートを追加したりすることです。 ファイルをダウンロードしたり、データベースを照会したり、他の種類の処理を行うコードは、QThreadのサブクラスに追加しないでください。 独自のオブジェクトにカプセル化する必要があります。」



「QThreadクラスは作成されており、オペレーティングシステムのスレッドへのインターフェイスとして使用することを目的としていますが、別のスレッドで実行するためのコードをそこに入れることは意図していません。 OOPでは、基本クラスの機能を拡張または深化するためにクラスを継承します。 私が想像できるQThread継承の唯一の正当化は、QThreadが存在しない機能を追加することです。たとえば、スレッドがスタックに使用できるメモリ領域にポインターを渡すこと、またはリアルタイムインターフェイスのサポートを追加することです。 ファイルの読み込み、データベースの操作、および同様の機能は、継承されたQThreadクラスに存在すべきではありません。 それらは他の施設に実装する必要があります”



つまり QThreadからの継承は完全に間違っているわけではありませんが、1つのクラスに異なる関数セットを不必要に混在させ、コードの可読性と保守性を低下させます。 しかし、QThreadの継承が間違っている場合、どのように正しいのでしょうか? 短い検索の後、私はこの投稿を見つけました。 ここではすべてが棚に配置されています。 投稿のキーポイント:

  1. QThreadはスレッドではなく、特定のOSスレッドのQtラッパーです。これにより、主にQtシグナル/スロットを介して、Qtプロジェクトのスレッドと対話できます。
  2. 別のスレッドで実行することを目的としたクラスのインスタンスへのnew演算子によるメモリの割り当ては、スレッドで既に実行する必要があります。 オブジェクトの所有者は、オブジェクトにメモリを割り当てたスレッドになります。
  3. ストリームとその中に「生きている」オブジェクトを管理するには、メッセージングを適切に構成することが重要です。




方法





したがって、スレッドでクラスを開始および停止するための「正しい」レシピ:



別のスレッドに存在するクラスのラッパーを作成します。 私たちの場合、これはReportBuilder



です。 それを包む: RBWorker





 class RBWorker : public QObject { Q_OBJECT private: ReportBuilder *rb; /*   */ QStringList file_list; /*     */ ReportType r_type; /*   */ public: RBWorker(ReportType p_type ); ~RBWorker(); void setFileList(const QStringList &files) { file_list = files; } /*      */ public slots: void process(); /*      */ void stop(); /*    */ signals: void finished(); /*       */ }; RBWorker:: RBWorker (ReportType p_type) { rb = NULL; r_type = p_type; } RBWorker::~ RBWorker () { if (rb != NULL) { delete rb; } } void RBWorker::process() { if(file_list.count() == 0) { emit finished(); return; } switch (r_type) { case REPORT_A: { rb = new ReportBuilderA (); break; } case REPORT_B: { rb = new ReportBuilderB (); break; } case REPORT_C: { rb = new ReportBuilderC (); break; } default: emit finished(); return ; } } rb->buildToFile(file_list); /*  buildToFile   rb->stop() */ emit finished(); return ; } void RBWorker::stop() { if(rb != NULL) { rb->stop(); } return ; }
      
      







重要な点: ReportBuilder



インスタンスは、 RBWorker



コンストラクターではなく、 process(),



メソッドprocess(),



作成されます。



Session



クラスはファイルの変更を追跡し、レポートを開始します。



 class Session : public QObject { Q_OBJECT public: Session(QObject *parent, const QString &directory, const QVector<ReportType> &p_rt); ~Session(); void buildReports(); private: void addThread(ReportType r_type); void stopThreads(); QStringList files; QVector<ReportType> reports; //  signals: void stopAll(); //   };
      
      







クラスで最も重要なメソッド: addThread







 void Session::addThread(ReportType r_type) { RBWorker* worker = new RBWorker(r_type); QThread* thread = new QThread; worker->setFileList(files); /*      */ worker->moveToThread(thread); /*     . : */ connect(thread, SIGNAL(started()), worker, SLOT(process())); /* …        process(),    ,       : */ connect(worker, SIGNAL(finished()), thread, SLOT(quit())); /* …      ,      finished() ,    quit() : */ connect(this, SIGNAL(stopAll()), worker, SLOT(stop())); /* …  Session         ,         finished()  : */ connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); /* …           : */ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); /* …      ,    .        .   : */ thread->start(); /*  ,   RBWorker::process(),   ReportBuilder     */ return ; } void Session::stopThreads() /*     */ { emit stopAll(); /*  RBWorker   ,        quit()   */ } void Session::buildReports() { stopThreads(); for(int i =0; i < reports.size(); ++i) { addThread(reports.at(i)); } return ; } void Session::~Session() { stopThreads(); /*         */ … }
      
      







実際には、すべてがすぐに機能しました。 Maya Poschに感謝します-それを理解するのに役立ちました。



All Articles