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()
,你将有未定义的行为,所以不要那样做。
如果任何数据类型是隐式共享容器,例如 QVector
或 QString
,您应该按值 return 它们,并且任何访问都是线程安全的:
QVector<MyData> data() const {
Q_ASSERT(! m_active);
return m_data;
}
请注意 QFile
是一个正确的 C++ class。当它被破坏时,它会释放持有的任何资源。手动关闭文件是不必要的:编译器应该在这里帮助你,这就是 C++ 对象模型与例如Java 的。
我正在尝试使用 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()
,你将有未定义的行为,所以不要那样做。
如果任何数据类型是隐式共享容器,例如 QVector
或 QString
,您应该按值 return 它们,并且任何访问都是线程安全的:
QVector<MyData> data() const {
Q_ASSERT(! m_active);
return m_data;
}
请注意 QFile
是一个正确的 C++ class。当它被破坏时,它会释放持有的任何资源。手动关闭文件是不必要的:编译器应该在这里帮助你,这就是 C++ 对象模型与例如Java 的。