Reputation: 4292
Lets say, I have a procedure called parallelRun
. It would take a list of workers, each having a getWorkAmount():int
, a run()
method, a finished()
signal and a cancel()
slot:
void parallelRun( std::vector< Worker* > workers );
Its implementation should:
QPogressDialog
:unsigned int totalWorkAmount = 0;
for( auto it = workers.begin(); it != workers.end(); ++it )
{
totalWorkAmount += ( **it ).getWorkAmount();
}
LoadUI ui( 0, totalWorkAmount, this );
with
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;
}
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 ) );
}
for( auto it = workers.begin(); it != workers.end(); ++it )
{
QMetaObject::invokeMethod( *it, "run", Qt::QueuedConnection );
}
load()
is run when the user clicks an UI-button.
How am I supposed to extend this code, if I want to make parallelRun
block until all workers are finished, without freezing the QProgressDialog
?
I tried adding the following code at the end of the parallelRun
routine:
QApplication::processEvents();
for( auto it = threads.begin(); it != threads.end(); ++it )
{
( **it ).wait();
}
The impact of this few lines of extra-code is, that LoadUI::progress
is never entered, since the GUI-thread is asleep and therefore it's event loop isn't processed: In Qt, signals are delivered to slots by posting them to the event loop of the thread, associated to the object the slot belongs to. This is why the progressed
signal of a worker is never delivered.
I think, the appropriate solution would be to run QApplication::processEvents()
within the GUI-thread anytime a progressed
signal is emitted by a worker. On the other hand, I guess this cannot be done, since the GUI-thread is asleep.
Another possibility would be to use an active waiting-like solution:
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();
}
This also requires adding the following line of code right after thread->start( priority );
:
while( !thread->isRunning() );
I don't think that this is a nice solution, but at least it works. How can this be done without the drawbacks of active waiting?
Thanks in advance!
Upvotes: 3
Views: 2987
Reputation: 29886
You could use the threads' finished()
signals to wait for them all to finish in the main GUI loop instead of using QApplication::processEvents
. The progress dialog modality will ensure that only that dialog window is active until it is explicitly closed.
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
}
}
};
Or you can run a nested QEventLoop
to wait for the threads to finish synchronously while still keeping the GUI responsive:
// 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();
If the progress reach the maximum only when the work is completely done, you can use progressDlg->exec()
instead of a QEventLoop
which will block until the maximum is reached or until the user clicks on the "Cancel" button.
Upvotes: 1
Reputation: 189
Instead of building your own. Maybe QThreadPool is what you are looking for?
QThreadPool has a function for waiting for all workers.
Upvotes: 1