QObject:无法为不同线程中的父对象创建子项

7

编辑:

我尝试按照评论中的建议进行操作……

Citizen * c = new Citizen(this);

QThread thread;
c->moveToThread(&thread);

connect(&thread, SIGNAL(started()), c, SLOT(ProcessActions()));
thread.start();

这会产生更多的错误:
QThread: Destroyed while thread is still running
ASSERT failure in QThread::setTerminationEnabled(): "Current thread was not started with QThread.", file c:\ndk_buildrepos\qt-desktop\src\corelib\thread\qthread_win.cpp, line 542
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
QObject::killTimers: timers cannot be stopped from another thread

我正在遇到这个错误的问题……我已经卡了两天了,但是无法找到解决方案。

头部:

class Citizen : public QThread
{
Q_OBJECT    
    QNetworkAccessManager * manager;

private slots:
    void onReplyFinished(QNetworkReply* net_reply);

public:
    Citizen(QObject * parent);

    void run();
};

实现:

Citizen::Citizen(QObject * parent)
{
    manager = new QNetworkAccessManager;
    connect(_net_acc_mgr, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(onReplyFinished(QNetworkReply*)));
}

void Citizen::onReplyFinished(QNetworkReply* net_reply)
{
    emit onFinished(net_reply);
}

void Citizen::run()
{
    manager->get(QNetworkRequest(QUrl("http://google.com"));

    QEventLoop eLoop;
    connect(manager, SIGNAL( finished( QNetworkReply * ) ), &eLoop, SLOT(quit()));
    eLoop.exec(QEventLoop::ExcludeUserInputEvents);

    qDebug() << "loaded google!";

    exec();
}

当执行manager->get()时,我会收到以下错误信息:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNetworkAccessManager(0xc996cf8), parent's thread is QThread(0xaba48d8), current thread is Citizen(0xca7ae08)

执行 eLoop.exec() 时:
QObject::startTimer: timers cannot be started from another thread

我以以下方式开始这个线程:

Citizen * c = new Citizen(this);
c->start();

为什么会出现这种问题?如何解决?


这个主题有一篇很好的文章Threads, Events and QObjects。如果你的Citizen类在一个线程中工作,你不应该从QThread继承它,因为从QThread继承的目的不是在线程中执行某些任务,而是管理线程。 - bartolo-otrit
6个回答

12
QObject: Cannot create children for a parent that is in a different thread.

您之所以会遇到这个问题,是因为您在Citizen的构造函数中创建了QNetworkAccessmanager,而该构造函数在“原始”线程中被调用。当Citizen对象移动到新线程时,QNetworkAccessmanager仍然将其线程亲和性设置为原始线程,但在run调用中,它将尝试在新线程中创建QNetworkReply对象(以及可能的其他对象)。这会产生上面的警告。
如果您在run槽中创建管理器(或在Citizen对象移动到新线程后的任何时间点),则不会发生这种情况。
但是您仍然有一些问题。例如,Citizen类确实不需要成为QThread。这只会使它变得更加复杂。对于您的目的(afaict),子类化QObject就足够了。只需创建一个普通的槽并将其连接到QThread :: started()信号即可。正如OrcunC指出的那样,您需要确保QThread实例的范围正确。
有关线程的更多信息:http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/ 示例:
QThread *thread = new QThread;
thread->start();
Citizen *worker = new Citizen;
worker->moveToThread(thread);

//startWorking can be equivalent of the run function
//in your current implementation and this is where you should
//create the QNetworkAccessManager
QMetaObject::invokeMethod(worker,"startWorking");

3
我会尽力回答为什么您会看到“QThread:在线程仍在运行时销毁”错误。如果您这样做:
void mtMethod () {

 Citizen * c = new Citizen(this);
 QThread thread;
 c->moveToThread(&thread);

 connect(&thread, SIGNAL(started()), c, SLOT(ProcessActions()));
 thread.start();
}

当您退出函数时,线程对象将被销毁,但已启动的线程仍在运行!Qt正在警告您应该停止线程或在更大的范围内创建线程对象(例如将其作为类的成员函数)。示例代码如下:

class myClass
{
virtual ~myClass ();
 QThread mythread;
};

myClass::~myClass
{
  mythread.stop ();
}

void myClass::mtMethod () {

     Citizen * c = new Citizen(this);
     c->moveToThread(&mythread);

     connect(&mythread, SIGNAL(started()), c, SLOT(ProcessActions()));
     mythread.start();
}

2
将父对象分配给一个随后移动到另一个线程的对象是不可行的。这个答案是不正确的。@Kim Bowles Sørhus的答案才应该被标记为正确的。 - rbaleksandar

1

在调用run()之前,我不相信新线程已经存在。因此,构造函数是一个不同于run()的线程。如果您将管理器对象的创建从构造函数移动到run()中会发生什么?我想如果不能解决计时器错误,至少可以解决第一个错误。

此外,我认为许多人仍然像您一样构建线程,但您可能需要查看this


不起作用,我也查看了那篇博客文章。请检查我的问题。 - liliumdev
哪个方法没有起作用?是将管理器的创建移出构造函数,还是按照链接中所述的方式使用moveToThread?我认为你的编辑不正确。如果你将Citizen作为被移动到QThread的对象,Citizen就不应该派生自QThread。 - Brett Stottlemyer
首先,你的QThread是在堆栈上创建的,在你退出启动线程的子程序后就会被销毁。我想你不希望这样。你应该将QThread作为类的成员。然后,你不需要显式调用quit方法。当exec返回时,你的线程将停止。 - Lol4t0
将管理器的创建移出构造函数并没有起作用。moveToThread 也不行,但正如你指出的那样,我可能使用不正确。我会按照你的方式尝试一下。 - liliumdev
我不明白如何将QThread作为类的成员? - liliumdev
显示剩余2条评论

1
你需要考虑 线程亲和性。那个错误信息并不是在撒谎或疯了,它告诉你确切地出了什么问题。

你的链接已经失效。 - Anon

1
你的问题大多是由于尝试子类化QThread引起的。即使文档推荐这样做,但这不是使用QThread的最佳方式。请参见this question and answer以获取更多信息和链接。

1

虽然可能与第一个错误有关,但我还没有解决startTimers错误。无论如何,您应该能够修复第一个错误。我在Qt中遇到过这个问题几次,我发现解决它的“最佳”方法是创建一个初始化函数和一个清理函数。类的所有成员都是指针,在调用run之前初始化为NULL。请注意,“最佳”加了引号,因为肯定会有不同的意见,但对于我来说,它适用于大多数情况。

标题

class Citizen : public QThread {
   Q_OBJECT

   QNetworkAccessManager * manager;

   private slots:
      void onReplyFinished(QNetworkReply* net_reply);
   public:
      Citizen(QObject * parent);
      void run();

   private:
      void initialize();
      void cleanUp();
 };

实现

Citizen::Citizen(QObject * parent) :
   manager(NULL) {
}

void Citizen::onReplyFinished(QNetworkReply* net_reply) {
   emit onFinished(net_reply);
}

void Citizen::run() {
   initialize();
   manager->get(QNetworkRequest(QUrl("http://google.com"));

   QEventLoop eLoop;
   connect(manager, SIGNAL( finished( QNetworkReply * ) ),
           &eLoop, SLOT(quit()));
   eLoop.exec(QEventLoop::ExcludeUserInputEvents);

   qDebug() << "loaded google!";
   exec();

   cleanUp();
}

void Citizen::initialize() {
   manager = new QNetworkAccessManager;
   connect(_net_acc_mgr, SIGNAL(finished(QNetworkReply*)),
           this, SLOT(onReplyFinished(QNetworkReply*)));
}

void Citizen::cleanUp() {
   delete manager;
   disconnect(_net_acc_mgr, SIGNAL(finished(QNetworkReply*)),
              this, SLOT(onReplyFinished(QNetworkReply*)));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接