线程中的事件循环

QT实现线程的方式有很多中,如果是QThread,本身自带事件循环,能够收到信号

如果是std::thread,是没有事件循环的需要在内部增加事件循环

发现这个问题是因为我在std::thread中使用QProcess发现没有收到信号,但是如果加了process->waitForFinished(-1)就能收到。 因为waitfor中带了事件循环QEventloop,如果不用waitfor的方式,那么我们可以这么写

    std::thread thread([=]{
        QDir dir(QCoreApplication::applicationDirPath());
        QString applicationPath = dir.path();
        dir.cdUp();
        QString PyScriptPath = dir.absolutePath() + "/python/3DSeg/AI3DNet";
        QString modelPath = dir.absolutePath() + "/python/3DSeg/model";
        QString pybinPath = dir.absolutePath() + "/python";
        QString auto3D = pybinPath + "/Auto3D/scripts/automatic_mask_generator_itk3D.py";
        QString port = "1520";
        QString url = QString("http://localhost:%1").arg(port);
        dir.cdUp();
        dir.cdUp();
        QString CTPyexePath = dir.absolutePath() + "/package/CTPython/python.exe";
        QString PyexePath = dir.absolutePath() + "/package/Python311/python.exe";
        QString Pymain = PyScriptPath + "/main.py";
        QString FinalIni = modelPath + "/final.ini";
        QString ModelFile = modelPath + "/demo.pth";
        QString LicenseFile = modelPath + "/license";
        QString LicenseDat = pybinPath + "/license.dat";

        QString path_cache = QCoreApplication::applicationDirPath() + "/cachePowder";
        QDir dir_cache;
        if (!dir_cache.exists(path_cache)) {
            if (dir_cache.mkpath(path_cache)) {
                qDebug() << "文件夹创建成功:" << path_cache;
            } else {
                qDebug() << "文件夹创建失败:" << path_cache;
            }
        }
        QString NpyFile = path_cache + "/image_npy.npy";
        QString ImageFile = path_cache + "/data.nii";
        saveToNII(ImageFile);
        QString resultFile = path_cache + "/result.nii";

        QStringList scripts;
        scripts << auto3D
                << "--SERVER" << url
                << "--PORT" << port
                << "--BATCH_SIZE" << QString::number(ui->spinBox_thread->value())
                << "--iou_threshold" << QString::number(ui->doubleSpinBox_apd->value())
                << "--start_index" << QString::number(ui->spinBox_start->value())
                << "--end_index" << QString::number(ui->spinBox_end->value())
                << "--precision" << QString::number(ui->doubleSpinBox_preci->value())
                << "--low_range" << QString::number(ui->doubleSpinBox_low->value())
                << "--upper_range" << QString::number(ui->doubleSpinBox_up->value())
                << "--low_threshold" << QString::number(-1000)
                << "--upper_threshold" << QString::number(8585)
                << "--hole_fill" << (ui->checkBox_hole->isChecked() ? "1" : "0")
                << "--save_iter" << (ui->checkBox_save->isChecked() ? "1" : "0")
                << "--direction" << QString::number(ui->comboBox_dir->currentIndex())
                << "--python_exe_file" << PyexePath
                << "--python_file_path" << Pymain
                << "--model3D_path" << FinalIni
                << "--model_path" << ModelFile
                << "--lisence_path" << LicenseFile
                << "--python_lisence_path" << LicenseDat
                << "--npy_save_path" << NpyFile
                << "--image_path" << ImageFile
                << "--save_path" << resultFile;

        QProcess* process = new QProcess;
        QEventLoop loop;
        QObject::connect(process, &QProcess::readyReadStandardOutput, [=]() {
            qDebug() << "Python out: " << process->readAllStandardOutput();
        });
        QObject::connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), [&](int exitCode, QProcess::ExitStatus exitStatus){
            loop.quit();
            qDebug() << "thread" << QThread::currentThread();
            qDebug() << exitCode << exitStatus;
            if(exitCode == 0) {
                QMetaObject::invokeMethod(this, [&](){
                    emit sig_fin(true);
                    Toast::showTip(Toast::SuccessToast, "AI计算完成");
                    this->accept();
                });
            } else {
                QMetaObject::invokeMethod(this, [&](){
                    emit sig_fin(false);
                    Toast::showTip(Toast::ErrorToast, "AI计算失败");
                    this->accept();
                });
            }
            QMetaObject::invokeMethod(this, [&](){
                m_wait->accept();
            });
        });

        QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
        env.insert("PYTHONPATH", pybinPath + "/Auto3D");
        process->setProcessEnvironment(env);
        process->setProcessChannelMode(QProcess::MergedChannels);
        qDebug() << "process work dir:" << process->workingDirectory();
        qDebug() << "ctpy:" << CTPyexePath << scripts;
        process->start(CTPyexePath, scripts);
//        process->waitForFinished(-1);
        loop.exec();
    });

下面看看chpt的分析

为什么 waitForFinished 时 readyRead 也能输出?

这个现象最迷惑人。

原因是:

waitForFinished 内部会偷偷处理事件

Qt 源码里 waitForFinished 会:

阻塞等待一小段时间

中间调用 processEvents()

让 readyRead 有机会触发

所以你看到:

waitForFinished(-1) 期间 readyRead 也能收到输出

但这只是 Qt “顺便帮你跑了一点事件循环”。

⚠️所以 waitForFinished 本质是:

阻塞等待 + 临时 pump 一下事件

补充说明一下,QProcess本身并不需要放到线程中,本身是异步的,但是我为了一个等待计算的进度条而这么做的。也可以如下

m_wait = new WaitDialog("AI计算中...", this);
m_wait->setWindowModality(Qt::ApplicationModal);
m_wait->show();

QProcess* process = new QProcess(this);
process->setProcessChannelMode(QProcess::MergedChannels);

connect(process, &QProcess::readyReadStandardOutput, this, [=](){
    qDebug() << process->readAllStandardOutput();
});

connect(process,
        static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
        this,
        [=](int exitCode, QProcess::ExitStatus)
{
    m_wait->close();          // ✅关闭等待框

    if(exitCode == 0)
        Toast::showTip(Toast::SuccessToast, "AI计算完成");
    else
        Toast::showTip(Toast::ErrorToast, "AI计算失败");

    process->deleteLater();
});
process->start(CTPyexePath, scripts);
Table of Contents