我试图使用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响应并且进度条正确更新,但是在输出中我收到错误&#34; QObject :: setParent:无法设置父级,新父级位于不同的线程中#34;很多时候,从循环中执行的东西。
任何想法导致这种情况,或建议更好地实施QtConcurrent :: run?
由于
答案 0 :(得分:1)
您无法触及工作线程中任何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和计算。相反,将工作人员抽象为没有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()
,那么您将有未定义的行为,所以不要这样做。
如果任何数据类型是隐式共享容器,如QVector
或QString
,则应按值返回它们,并且任何访问都是线程安全的:
QVector<MyData> data() const {
Q_ASSERT(! m_active);
return m_data;
}
请注意QFile
是一个合适的C ++类。它会释放在被破坏时保留的所有资源。手动关闭文件是不必要的:编译器应该在这里帮助你,这是C ++的对象模型的全部要点,例如Java的。