QtConcurrent::使用MainWindow功能运行,警告消息"QObject::setParent:

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

本文关键字:quot QObject setParent 消息 MainWindow 使用 功能 运行 QtConcurrent 警告      更新时间:2023-10-16

我正在尝试使用QtConcurrent::run来执行MainWindow类中的一个函数,以便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:无法设置父级,新的父级在不同的线程中",来自循环中执行的东西。

有什么想法导致了这种情况,或者对更好地实现QtConcurrent::run的建议吗?

感谢

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

相反,解决这一问题的方法是在工作线程中进行计算,然后向主线程提交更新状态的函数。有关详细信息,请参阅此答案。

然后你的代码会变成:

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代码不会在其他地方访问您从辅助程序更新的成员。这可能很难,因为您混合了UI和计算。相反,将工作者抽象到一个没有UI的QObject,并通过指示进度/状态的信号将其与其他代码接口。您仍将在该对象中使用QtConcurrent::run,但确保没有其他线程访问该对象的私有状态会变得更简单。

要将完成的结果从工作函数中推出,可以发出一个携带结果的信号。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,则应按值返回它们,并且任何访问都是线程安全的:

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

注意,QFile是一个适当的C++类。当它被销毁时,它会释放所有持有的资源。手动关闭文件是不必要的:编译器应该在这里帮助你,这就是C++的对象模型与Java的对象模型相比的全部意义。