QThread:使用 GUI 反馈阻止进行并行执行

QThread: Making parallel execution with GUI-Feedback blocking?

本文关键字:并行执行 使用 GUI QThread      更新时间:2023-10-16

>场景

比方说,我有一个名为 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 );
}

当用户单击 UI 按钮时运行load()

问题

如果我想在所有工作线程完成之前进行parallelRun块,而不冻结QProgressDialog,我应该如何扩展此代码?

审议

使用屏障

我尝试在parallelRun例程的末尾添加以下代码:

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

这几行额外代码的影响是,LoadUI::progress永远不会被输入,因为GUI线程处于睡眠状态,因此它的事件循环不会被处理:在Qt中,信号通过将信号发布到线程的事件循环来传递到插槽,与插槽所属的对象相关联。这就是为什么工人的progressed信号永远不会发出的原因。

我认为,适当的解决方案是在工作人员发出progressed信号时在 GUI 线程中运行QApplication::processEvents()。另一方面,我想这无法完成,因为 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() );

我不认为这是一个不错的解决方案,但至少它有效。如何在没有主动等待的缺点的情况下做到这一点?

提前感谢!

您可以使用

线程的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,该将阻止直到达到最大值或直到用户单击"取消"按钮。

而不是构建自己的。也许QThreadPool就是你要找的?

QThreadPool具有等待所有工人的功能。