QtConcurrent::run 带有 MainWindow 函数,警告消息 "QObject::setParent: Cannot set parent, new parent is in a different thread"

QtConcurrent::run with MainWindow function, warning message "QObject::setParent: Cannot set parent, new parent is in a different thread"

我正在尝试使用 QtConcurrent::run 在我的主窗口 class 中执行一个函数,以便 UI 在计算过程中保持响应。以下是我的实现方式:

    void MainWindow::on_calculate_pb_clicked()
    {
        QFuture<void> future = QtConcurrent::run(this,&MainWindow::calculation);
    }

    void MainWindow::calculation()
    {
        progressBar->show();
        loadMap();
        integral.clear();
        positions.clear();
        offset.clear();
        lines = 0;
        for(int i=0;i<paths.size();i++)
        {
            if(i ==0)
            {
                lines = countNumberOfLines(paths.at(i));
            }
            double file = i+1;
            ui->statusBar->showMessage(QString("Processing file %1 of %2").arg(file).arg(paths.size()));

            calculateIntegral(paths.at(i));
            offset.push_back(ui->tableWidget->item(i,1)->text().toDouble());
        }
        makePositionVector();

        plotData(ui->customPlot);
        ui->export_pb->setEnabled(true);
        progressBar->hide();
        ui->statusBar->showMessage("Done",3000);

    }

    void MainWindow::calculateIntegral(QString path)
    {
        QVector<double> mappeddata,tempdata;
        mappeddata.resize(map.size());
        tempdata.resize(numberofdetectors);

        double currentLine = 0;

        QFile File(path);
        if(File.exists()==true){
            File.open(QIODevice::ReadOnly);
            QTextStream in(&File);
            double val;

            while(!in.atEnd())
            {
                for(int j = 0;j<numberofdetectors;j++)
                {
                    in >> val;
                    tempdata[j]+=val;
                    currentLine++;
                    double progress = currentLine/lines*100;
                    progressBar->setValue(progress);
                }

            }

            for(int i =0;i<map.size();i++)
            {
                mappeddata[i] = tempdata.at(map.at(i));
            }
            for(int k = 0;k<numberofdetectors; k++)
            {
                integral.push_back(mappeddata.at(k));
            }

        }
        File.close();
    }

它工作正常并且 UI 响应迅速并且进度条正确更新,但是在输出中我收到错误 "QObject::setParent: Cannot set parent, new parent is in a different thread" 很多次,来自循环中执行的某些东西。

知道是什么原因造成的,或者有更好地实施 QtConcurrent::run 的建议吗?

谢谢

您不能从工作线程中接触任何 Qt 提供的 QWidget 对象,因为它们的大多数方法都不是线程安全的。

相反,解决此问题的一种方法是在 worker 中进行计算,然后将更新状态的仿函数提交到主线程。有关详细信息,请参阅 this answer

您的代码将变为:

void MainWindow::calculation()
{
    postToThread([this]{ progressBar->show(); });
    loadMap();
    integral.clear();
    positions.clear();
    offset.clear();
    lines = 0;
    for(int i=0;i<paths.size();i++)
    {
        if (i == 0)
            lines = countNumberOfLines(paths.at(i));
        auto file = i+1;
        postToThread([this]{
            ui->statusBar->showMessage(
              tr("Processing file %1 of %2").arg(file).arg(paths.size()));
        });
        calculateIntegral(paths.at(i));
        postToThread([this]{
          offset.push_back(ui->tableWidget->item(i,1)->text().toDouble());
        });
    }
    makePositionVector();

    postToThread([this]{
      plotData(ui->customPlot);
      ui->export_pb->setEnabled(true);
      progressBar->hide();
      ui->statusBar->showMessage("Done",3000);
    });
}

以类似的方式修改 calculateIntegral,但请确保不要过于频繁地发布进度更新。

还要确保 UI 代码不会在别处访问您从 worker 更新的成员。这可能很难,因为您混合了 UI 和计算。相反,将 worker 抽象为没有 UI 的 QObject,并通过指示 progress/status 的信号将其连接到其他代码。您仍将在该对象中使用 QtConcurrent::run,但确保没有其他线程访问该对象的私有状态变得更简单。

要将完成的结果从 worker 仿函数中推出,您可以发出一个包含结果的信号。 Data 类型复制起来应该很便宜,例如您可以使用 QSharedData/QSharedDataPointer 来实现它。或者你可以通过 QSharedPointer.

持有它
class Computation : public QObject {
  Q_OBJECT
  void work() {
    Data data;
    ... // updates data
    emit finished(data);
  }
public:
  Q_SLOT void compute() {
    QtConcurrent::run(&Worker::work, this);
  }
  Q_SIGNAL void finished(Data data);
};

您也可以将结果存储在对象中,注意在计算活动时不能访问它们:

class Computation : public QObject {
  Q_OBJECT
  bool m_active { false };
  Data m_data;
  void work() {
    ... // updates m_data
    m_active = false;
  }
public:
  Q_SLOT void compute() {
    m_active = true;
    QtConcurrent::run(&Worker::work, this);
  }
  const Data & data() const {
    Q_ASSERT(! m_active);
    return m_data;
  }
};

当然,如果你在主线程中存储对data()的引用,然后调用compute(),你将有未定义的行为,所以不要那样做。

如果任何数据类型是隐式共享容器,例如 QVectorQString,您应该按值 return 它们,并且任何访问都是线程安全的:

  QVector<MyData> data() const {
    Q_ASSERT(! m_active);
   return m_data;
  } 

请注意 QFile 是一个正确的 C++ class。当它被破坏时,它会释放持有的任何资源。手动关闭文件是不必要的:编译器应该在这里帮助你,这就是 C++ 对象模型与例如Java 的。