QMLの基礎 - マルチスレッド

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

QMLとC++を連携させたマルチスレッドの実装手順、および、WorkerScriptを使用したマルチスレッドの実装手順を記載する。


WokerScriptの使用

WokerScriptでやり取りできるデータ型

  • bool型、number型、string
  • JavaScriptオブジェクトと配列
  • ListModel(他のオブジェクトは不可)


また、WorkerScriptListModelにアイテムを更新した時は、syncメソッドで反映できる。

処理の手順

  1. メインスレッドからWokerScriptを起動する時は、sendMessage(jsobject)を使用する。
  2. WokerScript側のメソッドは、WorkerScript.onMessage(jsobject)を使用する。
  3. WokerScriptからメインスレッドへ通信する時は、WorkerScript.sendMessage(jsobject)を使用する。
  4. メインスレッド側で受けるonMessageシグナルの引数は、messageObjectを使用する。


サンプルコード

以下の例では、[Click]を押下する時、1秒間隔でリストへアイテムを追加している。
アイテムを追加している最中でも、リストをドラッグして動かすこともできる。

QML MultiThread 1.png
 // main.qml
 
 import QtQuick 2.15
 
 Rectangle {
    id: _root
    width: 200
    height: 360
 
    // ボタン
    Rectangle {
       id: _btn
       width: _root.width
       height: _lavel.paintedHeight * 2
       color: "#ddddff"
       border.color: "#dddddd"
       border.width: 1
 
       Text {
          id: _lavel
          anchors.centerIn: parent
          text: "Click!"
       }
 
       // スレッド開始処理
       MouseArea {
          anchors.fill: parent
          onClicked: {
             console.debug("click");
             // スレッド開始
             _thread.sendMessage({"model":_model, "number": 20, "string": "Start!"});
          }
       }
    }
 
    // スレッド用エレメント
    WorkerScript {
       id: _thread
       source: "scripts.js"     // スレッド処理をするスクリプトファイルの指定
 
       onMessage: {
          // Workerスレッドからの通信イベント
          console.debug("finish thread:" + messageObject.result + ", split=" + messageObject.split);
       }
    }
 
    // リスト
    ListView {
       anchors.top: _btn.bottom
       anchors.left: _root.left
       anchors.right: _root.right
       anchors.bottom: _root.bottom
       clip: true
 
       model: _model
       delegate: _delegate
    }
 
    // リストの初期アイテム
    ListModel {
       id: _model
 
       ListElement {
          _message: "Item"
       }
    }
 
    // リスト用レイアウト
    Component {
       id: _delegate
 
       Rectangle {
          width: _root.width
          height: _text.paintedHeight * 2
          border.color: "#dddddd"
          border.width: 1
 
          Text {
             id: _text
             anchors.centerIn: parent
             text: _message
          }
       }
    }
 }


 // scripts.js
 
 WorkerScript.onMessage = function(message)
 {
    console.debug("start worker script:" + message.number + "," + message.string);
 
    var now = 0;
    var split = new Date();
 
    for(var i = 0; i < message.number; i++)
    {
       delay(1000);
 
       now = new Date();
       message.model.append({"_message": i + " : " + Qt.formatDateTime(now, "yyyy/MM/dd hh:mm:ss")});
       message.model.sync();
    }
 
    split = (new Date()) - split;
 
    // メインスレッドへの応答
    WorkerScript.sendMessage({ "result": "tRue", "split": split })
 }
 
 // ウエイトを入れる
 function delay(msec)
 {
    var start = 0;
    start = new Date();
 
    do
    {
       now = new Date();
    }while((now - start) < msec);
 }



QMLとC++を連携させたマルチスレッド

QObjectクラスを継承したクラスを作成およびそのクラス内にワーカースレッドを作成して、メインスレッドをブロックせずにQML側と非同期に通信することができる。

以下の例では、ワーカースレッドを5秒停止かつシグナルを発信しながら、メインスレッドで画面を更新し続けている。
ワーカースレッドの実行中において、一時停止、中断、制御は一切できないため、それらが必要な場合はノンブロッキングワーカースレッドを実装する必要がある。

 // CController.h
 
 #include <QThread>
 #include <QString>
 #include <thread>
 #include <memory>
 
 class CWorkerThread : public QObject
 {
    Q_OBJECT
 
 public slots:
    void doWork()
    {
       int i = 0;
       while (i < 10)
       {
          emit result(i++, tr("hoge"));
          std::this_thread::sleep_for(std::chrono::milliseconds(500));
       }
    }
 
 signals:
    void result(int r, QString s);
 };
 
 class CController : public QObject
 {
    Q_OBJECT
 
 public:
    Controller()
    {
       m_pWorker = std::make_unique<CWorkerThread>();
       m_pThread = std::make_unique<QThread>();
 
       m_pWorker->moveToThread(m_pThread.get());
 
       connect(this, SIGNAL(start()), m_pWorker.get(), SLOT(doWork()));
       connect(m_pWorker.get(), SIGNAL(result(int,QString)), this, SIGNAL(result(int,QString)));
 
       m_pThread->start();
    }
 
 private:
    std::unique_ptr<CWorkerThread>  m_pWorker;
    std::unique_ptr<QThread>        m_pThread;
 
 signals:
    void start();
    void result(int r, QString s);
 };


 // main.cpp
 
 // ...略
 
 qmlRegisterType<CController>("Controller", 1, 0, "CController");
 
 QQmlApplicationEngine engine;
 const QUrl url(QStringLiteral("qrc:/main.qml")); 
 
 // ...略


 // main.qml
 
 import QtQuick 2.15
 import QtQuick.Window 2.15
 import QtQuick.Controls 2.15
 import Controller 1.0
 
 
 ApplicationWindow {
    id: mainWindow
 
    // ...略
 
    CController {
        id: mainController;
    }
 
    Column {
       Button {
          id: start
 
          onClicked: {
             mainController.start()
          }
       }
 
       Text {
          id: res
       }
    }
 
    // Result of Slot
    Connections {
       target: mainController
 
       function onResult(r, s) {
          res.text = r + qsTr(":") s;
       }
    }
 
    // ...略
 }