QThread: выполнение параллельного выполнения с блокировкой GUI-обратной связи?

Сценарий

Допустим, у меня есть процедура под названием parallelRun. Потребуется список воркеров, у каждого из которых есть getWorkAmount():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. Создайте один поток для каждого работника

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() запускается, когда пользователь нажимает кнопку пользовательского интерфейса.

Проблема

Как мне расширить этот код, если я хочу сделать блок parallelRun до тех пор, пока не закончатся все рабочие процессы, не замораживая QProgressDialog?

Обсуждения

Использование барьера

Я попытался добавить следующий код в конец подпрограммы parallelRun:

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

Влияние этих нескольких строк дополнительного кода заключается в том, что LoadUI::progress никогда не вводится, так как GUI-поток спит и, следовательно, его цикл обработки событий не обрабатывается: Qt сигналы доставляются в слоты путем отправки их в цикл обработки событий потока, связанного с объектом, которому принадлежит слот. Вот почему сигнал 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() );

Я не думаю, что это хорошее решение, но, по крайней мере, оно работает. Как это можно сделать без недостатков активного ожидания?

Заранее спасибо!


person theV0ID    schedule 06.07.2012    source источник


Ответы (2)


Вы можете использовать сигналы потоков finished(), чтобы дождаться их завершения в основном цикле графического интерфейса вместо использования 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, чтобы дождаться синхронного завершения потоков, сохраняя при этом отзывчивость графического интерфейса:

// 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, который будет блокироваться до тех пор, пока не будет достигнут максимум или пока пользователь не нажмет кнопку «Отмена».

person alexisdm    schedule 06.07.2012
comment
Я не совсем уверен, как это должно мне помочь. Как бы я использовал это в подпрограмме (которая должна блокироваться на время операции) без замораживания QProgressBar? - person theV0ID; 06.07.2012
comment
Вы не блокируете свой графический интерфейс (т.е. основной поток). Пользователь не может использовать графический интерфейс, потому что диалоговое окно модального индикатора выполнения находится впереди и перекрывает остальную часть вашего графического интерфейса. Таким образом, основной поток все еще работает и может обновлять индикатор выполнения до тех пор, пока сигнал finish() не закроет его. - person grefab; 06.07.2012
comment
Верно, но моя цель состоит в том, чтобы сделать мою parallelRun рутинную блокировку. Например, подумайте о QFileDialog. Если вы вызываете его метод getOpenFileName из потока пользовательского интерфейса, выполнение кода блокируется до тех пор, пока пользователь не выберет файл для открытия или не прервет диалог. Следовательно, это метод блокировки, тем не менее пользовательский интерфейс не замораживается, пользователь может взаимодействовать с диалогом. Мне нужно аналогичное поведение, только мне нужно выполнить некоторые вычислительные задачи (и представить их текущий прогресс пользователю) вместо представления диалогового окна выбора файла. - person theV0ID; 08.07.2012
comment
@ theV0ID Я отредактировал ответ. Но блокировка с помощью такого вложенного цикла событий может вызвать некоторые проблемы (см. Непредсказуемый exec() (qt labs) или вложенные циклы событий< /а>). - person alexisdm; 09.07.2012

Вместо того, чтобы строить свои собственные. Может быть, QThreadPool — это то, что вы ищете?

QThreadPool имеет функцию ожидания всех воркеров.

person Tobias Hieta    schedule 06.07.2012
comment
Как это должно решить мою проблему? Не могли бы вы привести пример? - person theV0ID; 06.07.2012
comment
@theV0ID Это ничего не решит, функции ожидания блокируются, поэтому это будет то же самое, что и ваше собственное барьерное решение. - person alexisdm; 06.07.2012