Сценарий
Допустим, у меня есть процедура под названием 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() );
Я не думаю, что это хорошее решение, но, по крайней мере, оно работает. Как это можно сделать без недостатков активного ожидания?
Заранее спасибо!