7

私の Qt アプリケーションQThreadでは、重い計算タスクを定期的に実行する必要がある を作成します。メインQApplicationスレッドは、GUI (例には含まれていません) を維持し、定期的な更新も実行することになっています。両方のスレッドには、通常の update() 呼び出しを有効にする独自のタイマーがあります。

問題:ワーカー スレッドの計算ワークロードが重大な値を超えると、メイン スレッドがタイマー イベントの受信を停止します。

コード例を以下に示します。メインスレッドの場合は update() が呼び出されたときに「Main」、ワーカースレッドの場合は「Worker」を出力します。実行すると、「Worker」が定期的に出力され、「Main」が正確に 2 回 (最初に 1 回、約 5 秒に 1 回) 表示されることがわかります。フル機能の GUI アプリケーションの場合、これは事実上、GUI が完全にフリーズすることを意味します。

いくつかの観察。

  1. 内部サイクルに (1000 ではなく) 100 制限を設定してワークロードを削減すると、問題が解決します (両方の update() メソッドが定期的に呼び出されます)。
  2. ワーカー スレッド タイマー シグナルの接続タイプを Qt::DirectConnection に設定すると、問題が解決します。

ご覧のとおり、これにはいくつかの回避策がありますが、元のコードの問題を説明してくれる人がいれば幸いです。スレッドがイベント ループを個別に実行することを期待しています。長い update() 操作によってワーカー スレッド イベント ループをブロックしていることはわかっていますが、一体なぜそれがメイン スレッドに影響を与えるのでしょうか?

QConcurrentPS はい、私は代替案について知っています。しかし、私は理解したいと思います。

test.cpp

#include <windows.h>
#include <QApplication>

#include "test.h"

HANDLE mainThread_ = INVALID_HANDLE_VALUE;
QApplication *app_ = 0;
MyObj *obj_ = 0;
MyThread *thread_ = 0;

MyObj::MyObj()
    : timer_(0)
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);
}

void MyObj::update()
{
    printf("Main\n");
}

void MyThread::run()
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);

    exec();
}

void MyThread::update()
{
    printf("Worker\n");

    // do some hard work
    float f = 0.f;
    for (int i=0; i < 100000; ++i)
    {
        for (int j=0; j < 1000; ++j)
        {
            f += i * j;
        }
    }
}

int main()
{
    int argc = 0;
    app_ = new QApplication(argc, 0);

    obj_ = new MyObj();
    thread_ = new MyThread();
    thread_->start();

    QApplication::exec();

    return 0;
}

test.h

#include <QTimer>
#include <QThread>

class MyObj : public QObject
{
    Q_OBJECT

public:
    MyObj();

public slots:
    void update();

private:
    QTimer *timer_;
};

class MyThread : public QThread
{
    Q_OBJECT

public:
    void run();

public slots:
    void update();

private:
    QTimer *timer_;
};

UPD:立派なメンバーからいくつかの回答を得ました (以下をお読みください)。ここで、特にどのような誤った考えが私のコードを壊したのかを明確にしたいと思います。

ご覧のとおり、2 つのスレッドがそれぞれ何らかの update() プロシージャを定期的に実行する計画でした。私の間違いは、 update() を単なる手順として考えていたことであり、それはslotです。独自のスレッド アフィニティを持つ特定のオブジェクトのスロット。つまり、その本体はそのスレッドで実行されます (信号が Qt::DirectConnection でディスパッチされない限り)。今、私はタイマーでそれをすべてうまくやったようです-それらのそれぞれは異なるスレッドに属しています-しかしupdate()で物事を台無しにしました。そのため、メインスレッドで両方の update() プロシージャを実行することになりました。明らかに、ある時点でイベント ループがタイマー イベントであふれ、決して反復が終了しません。

解決策については。「あなたのやり方は間違っている」を読んだことがあるなら (実際そうすべきです)、すべてのロジックを QThread からサブクラス化されていないが、個別に作成されて moveToThread( )。個人的には、オブジェクトがスレッドを制御するだけでスレッドに属していないことを覚えていれば、QThread からサブクラス化しても問題はないと思います。したがって、そのスレッドで実行したいコードの場所ではありません。

4

2 に答える 2

3

ここでの最初の問題は、QThread から継承していることです。そのため、ここで述べているように、「あなたは間違っています」。

発生している問題は、スレッド アフィニティ (オブジェクトが実行されているスレッド) に起因します。たとえば、QThread から継承してコンストラクターでオブジェクトを作成する場合、オブジェクトを親にすることなく、そのオブジェクトは新しいスレッドではなくメイン スレッドで実行されます。したがって、MyThread コンストラクターでは次のようになります。

MyThread::MyThread()
    : timer_(0)
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);
}

ここでのタイマー (timer_) は、新しいスレッドではなく、メイン スレッドで実行されます。繰り返しを省くために、以前の回答の 1 つでスレッド アフィニティについて説明しています

この問題を解決する最善の方法は、QObject から継承するようにクラスを変更し、そのオブジェクトを新しいスレッドに移動することです。

于 2013-10-22T13:42:35.627 に答える