QtConcurrent::blockingMap vs QtConcurrent::map and waitForFi

QtConcurrent::blockingMap vs QtConcurrent::map and waitForFinished

本文关键字:QtConcurrent waitForFi and vs blockingMap map      更新时间:2023-10-16

Qt文档对QtConcurrent::blockingMap有如下说明:

注意:此功能将阻止,直到处理完序列中的所有项目。

QtConcurrent::map 的文档在其他方面是相同的。此外,它还返回QFuture<void>而不是void

QFuture 的文档有以下注释:

waitForDone() 函数使调用线程阻塞并等待计算完成,从而确保所有结果都可用。

因此,我希望QtConcurrent::blockingMap(seq, f)QtConcurrent::map(seq, f).waitForFinished()相同。然而,事实并非如此。

#include <QObject>
#include <QtConcurrent>
class Foo : public QObject {
Q_OBJECT
public:
    explicit Foo(QObject *parent = nullptr) : QObject(parent) {
        connect(this, &Foo::signal, this, &Foo::slot, Qt::AutoConnection);
    }
    void startMapWithWaiting() {
        qDebug("startMapWithWaiting");
        slot_counter = 0;
        std::atomic_int signal_counter = 0;
        QtConcurrent::map(nums, [&](auto &&num) {
            ++signal_counter;
            emit signal();
        }).waitForFinished();
        qDebug("result: %d signals, %d slots", int(signal_counter), int(slot_counter));
        slot_counter = 0;
    }
    void startBlockingMap() {
        qDebug("startBlockingMap");
        slot_counter = 0;
        std::atomic_int signal_counter = 0;
        QtConcurrent::blockingMap(nums, [&](auto &&num) {
            ++signal_counter;
            emit signal();
        });
        qDebug("result: %d signals, %d slots", int(signal_counter), int(slot_counter));
        slot_counter = 0;
    }
public slots:
    void slot() { ++slot_counter; }
signals:
    void signal();
private:
    std::atomic_int slot_counter = 0;
    std::vector<int> nums{1, 2, 5, 8};
};
#include "main.moc"
int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    Foo *foo = new Foo(&app);
    QTimer::singleShot(10, foo, [foo, &app]() {
        foo->startMapWithWaiting();
        foo->startBlockingMap();
        app.quit();
    });
    return app.exec();
}

输出为

startMapWithWaiting
result: 4 signals, 4 slots
startBlockingMap
result: 4 signals, x slots

其中x从 0 到 4 不等,具体取决于...东西。这令人困惑。

想知道这两种方式之间有什么区别以及我是如何误读文档的。

这两种方法的工作方式相同:所有 lambda 函数在发出 4 个信号时终止。示例中调用的插槽的差异取决于emit signal()的工作方式。由于程序用于信号/插槽Qt::AutoConnection

  • 如果目标线程(创建Foo的主线程)与当前线程(池线程,由全局ThreadPool控制)不同,则该事件将被放入事件队列中,否则,该插槽将直接执行(与Qt::DirectConnection相同)。

使用所示的相同示例可以获得不同的结果(x 插槽)。这取决于全局线程池如何管理其线程。在我的配置中,输出是:

    startMapWithWaiting
    result : 4 signals, 0 slots
    startBlockingMap
    result : 4 signals, 4 slots

为了得到相同的结果,我们可以在打印结果之前使用 Qt::D irectConnection 而不是 Qt::AutoConnection 或调整 QApplication::p rocessEvent():

    QApplication::processEvents(); //<-- force slot() to be processed.
    qDebug("result blocking : %d signals, %d slots", int(signal_counter), int(slot_counter));

blockingMap保证不会在主线程(应用程序"生活"的地方)中执行任何其他操作。因此,它可能使用主线程和池线程,而QtConcurrent::map无法在主线程中执行 lambda。这就是导致不同结果的原因。实际上,x是在主线程中执行 lambda 的次数。原因如下:

当接收器lives发出信号的同一线程中(在我的例子中,在主线程中)时,Qt::AutoConnection会立即调用信号,因此slot_counter会更新。当接收方lives另一个线程时,调用槽将排队,并将在startBlockingMapstartMapWithWaiting完成时进行处理。为了立即处理它们,可以按照tungit的建议调用qApp->processEvents()