3

シナリオ

というプロシージャがあるとしましょうparallelRungetWorkAmount():intそれぞれが、run()メソッド、finished()シグナル、およびcancel()スロットを持つワーカーのリストを取得します。

void parallelRun( std::vector< Worker* > workers );

その実装は次のことを行う必要があります。

1. を開きますQPogressDialog:

unsigned int totalWorkAmount = 0;
for( auto it = workers.begin(); it != workers.end(); ++it )
{
    totalWorkAmount += ( **it ).getWorkAmount();
}

LoadUI ui( 0, totalWorkAmount, this );

class LoadUI : public QObject
{
    Q_OBJECT

public:

    LoadUI( int min, int max, QWidget* modalParent )
        : totalProgres( 0 )
        , progressDlg( "Working", "Abort", min, max, modalParent )
    {
        connect( &progressDlg, SIGNAL( canceled() ), this, SLOT( cancel() ) );

        progressDlg.setWindowModality( Qt::WindowModal );
        progressDlg.show();
    }

    bool wasCanceled() const
    {
        return progressDlg.wasCanceled();
    }

public slots:

    void progress( int amount )
    {
        totalProgres += amount;

        progressDlg.setValue( totalProgres );
        progressDlg.update();

        QApplication::processEvents();
    }

signals:

    void canceled();

private slots:

    void cancel()
    {
        emit canceled();
    }

private:

    int totalProgres;
    QProgressDialog progressDlg;
}

2. ワーカーごとに 1 つのスレッドを作成する

std::vector< std::unique_ptr< QThread > > threads;
for( auto it = workers.begin(); it != workers.end(); ++it )
{
    std::unique_ptr< QThread > thread( new QThread() );

    Worker* const worker = *it;
    worker->moveToThread( thread.get() );

    QObject::connect( worker, SIGNAL( finished() ), thread.get(), SLOT( quit() ) );
    QObject::connect( &ui, SIGNAL( canceled() ), worker, SLOT( cancel() ) );
    QObject::connect( *it, SIGNAL( progressed( int ) ), &ui, SLOT( progress( int ) ) );

    thread->start( priority );

    threads.push_back( std::move( thread ) );
}

3. 同時に実行する

for( auto it = workers.begin(); it != workers.end(); ++it )
{
    QMetaObject::invokeMethod( *it, "run", Qt::QueuedConnection );
}

load()ユーザーが UI ボタン​​をクリックすると実行されます。

問題

parallelRunをフリーズせずに、すべてのワーカーが終了するまでブロックしたい場合、このコードをどのように拡張すればよいQProgressDialogですか?

審議

バリアの使用

parallelRunルーチンの最後に次のコードを追加してみました。

QApplication::processEvents();
for( auto it = threads.begin(); it != threads.end(); ++it )
{
    ( **it ).wait();
}

この数行の追加コードの影響は、GUI スレッドがスリープしているため、入力LoadUI::progressされないため、イベント ループが処理されないことです。スロットが属するオブジェクトに関連付けられたスレッド。これが、ワーカーのシグナルが配信されない理由です。progressed

適切な解決策は、ワーカーによってシグナルが発行されるたびQApplication::processEvents() に GUI スレッド内で実行することだと思います。progressed一方、GUI スレッドがスリープしているため、これは実行できないと思います。

別の可能な解決策

別の可能性は、アクティブな待機のようなソリューションを使用することです。

for( auto it = threads.begin(); it != threads.end(); ++it )
{
    while( ( **it ).isRunning() )
    {
        QApplication::processEvents();
    }
}
for( auto it = threads.begin(); it != threads.end(); ++it )
{
    ( **it ).wait();
}

これには、 の直後に次のコード行を追加する必要もありますthread->start( priority );

while( !thread->isRunning() );

これは良い解決策だとは思いませんが、少なくとも機能します。アクティブな待機の欠点なしでこれを行うにはどうすればよいでしょうか?

前もって感謝します!

4

2 に答える 2

1

を使用する代わりに、スレッドのfinished()シグナルを使用して、メインの GUI ループですべてのスレッドが終了するのを待つことができますQApplication::processEvents。進行状況ダイアログのモダリティにより、明示的に閉じられるまで、そのダイアログ ウィンドウのみがアクティブになります。

class WorkerManager : public QObject {
    Q_OBJECT
private:
    // to be able to access the threads and ui, they are defined as a members
    std::vector<std::unique_ptr<QThread> > threads;
    LoadUI *ui;

    int finishedThreadCount;
public:
    WorkerManager() 
        : finishedThreadCount(0)
    {
        // Open the QProgressDialog
        ...
        // Create and start the threads
        ...
        // Connect the finished() signal of each thread 
        // to the slot onThreadFinished
        for( auto it = threads.begin(); it != threads.end(); ++it )  {
            QObject::connect(
                it->get(), SIGNAL(finished()), 
                this, SLOT(onThreadFinished()) );
        }
    }

private slots:
    void onThreadFinished() {
         ++finishedThreadCount;

         if(finishedThreadCount == threads.size()) 
         {
              // clean up the threads if necessary
              // close the dialog
              // and eventually destroy the object this itself
         }
    }
};

または、ネストされた を実行しQEventLoopて、GUI の応答性を維持しながら、スレッドが同期的に終了するのを待つことができます。

// Open the QProgressDialog
...
// Create and start the threads
...
// Create and run a local event loop,
// which will be interrupted each time a thread finishes
QEventLoop loop;
for( auto it = threads.begin(); it != threads.end(); ++it )  
{
    QObject::connect(
        it->get(), SIGNAL(finished()), 
        &loop, SLOT(quit()) );
}  
for(int i = 0, threadCount = threads.size(); i < threadCount; ++i) 
    loop.exec();

作業が完全に完了したときにのみ進行状況が最大に達する場合は、最大に達するまで、またはユーザーが [キャンセル] ボタンをクリックするまでブロックする のprogressDlg->exec()代わりに使用できます。QEventLoop

于 2012-07-06T12:50:45.557 に答える
1

独自のものを構築する代わりに。多分 QThreadPool はあなたが探しているものですか?

QThreadPool には、すべてのワーカーを待機する機能があります。

于 2012-07-06T11:00:29.423 に答える