简单说说对QT中moveToThread实现多线程操作的理解

  在平时的编码过程中经常碰到QT的多线程问题,也大量接触了QT中的两种主流多线程写法,一种是继承QThread类并重载run函数,在run函数中写一个状态机或者计时器来实现对线程运作;一种是通过moveToThread的方式实现事件托管从而实现线程运作。前者虽然是传统写法但是弊病较多,这里主要针对后者来进行说明与理解。
  1.常见情况分析:该方法在进行代码编写时的常见情况如下:

class tempActive
{
public:
	tempActive()
    {
    	...
    	a = new A;
    	m_thread = new QThread;
    	a->moveToThread(m_thread);
    	...
    ]
private:
	A* a;
	QThread* m_thread;
    ...
};

  上述代码中,tempActive为某一工作相关类,A为某一自定义类或预定义类,其实例a与实例m_thread往往在tempActive的构造函数中实现,并在其中将a实例moveToThread至m_thread线程之中。这里需要着重理解一点,并非是将a实例相关的所有的工作“移动”到了m_thread线程,而是将所有a实例相关的事件托管到m_thread线程执行。换句话说,就是通过信号槽connect或者invokeMethod触发a实例中槽函数产生的事件,将会被放置到m_thread线程中执行,从而实现了多线程工作。也就是说,此时通过a->show()等方式调用直接调用a实例中的函数,无法实现多线程功能。
  2.实验验证:为了验证以及加强理解上述结论,在这里做一个简单的实验如下:

class A : public QObject
{
    Q_OBJECT
public:

public slots:
    void show()
    {
        qDebug() << "show:" << QThread::currentThreadId();
    }
};

class MainWindow : public QObject
{
    Q_OBJECT

public:
    explicit MainWindow(QObject *parent = 0);
    ~MainWindow();
public slots:
    void mainShow() {qDebug() << "mainShow:" << QThread::currentThreadId();}
signals:
    void s_test();
private:
    A* a;
    QTimer* m_timer;
    QThread* m_thread;
};

MainWindow::MainWindow(QObject *parent) :
    QObject(parent)
{
    a = new A;
    m_thread = new QThread;
    m_timer = new QTimer;
    m_timer->setInterval(500);
    m_thread->start();

    qDebug() << "A before move:" << QThread::currentThreadId();	//A before move: 0x2a0c
    a->show();	//show: 0x2a0c
    QMetaObject::invokeMethod(a,"show",Qt::DirectConnection);	//show: 0x2a0c
    QObject::connect(this,SIGNAL(s_test()),a,SLOT(show()),Qt::DirectConnection);
    emit s_test();	//show: 0x2a0c
    QObject::disconnect(this,SIGNAL(s_test()),a,SLOT(show()));

    a->moveToThread(m_thread);
    qDebug() << "A after move:" << QThread::currentThreadId();	//A after move: 0x2a0c
    a->show();	//show: 0x2a0c
    QMetaObject::invokeMethod(a,"show",Qt::BlockingQueuedConnection);	//show: 0x3a9c
    QObject::connect(this,SIGNAL(s_test()),a,SLOT(show()),Qt::BlockingQueuedConnection);
    emit s_test();	//show: 0x3a9c
    QObject::disconnect(this,SIGNAL(s_test()),a,SLOT(show()));

    qDebug() << "m_timer before move:" << QThread::currentThreadId();	//m_timer before move: 0x2a0c
    m_timer->moveToThread(m_thread);
    this->moveToThread(m_thread);
    qDebug() << "m_timer after move:" << QThread::currentThreadId();	//m_timer after move: 0x2a0c
    QObject::connect(m_timer,SIGNAL(timeout()),this,SLOT(mainShow()),Qt::DirectConnection);	
    QMetaObject::invokeMethod(m_timer,"start",Qt::BlockingQueuedConnection);	//mainShow: 0x3a9c
}

  在上述函数中,通过MainWindow的构造函数进行实验,这里分步分析下:

  • 当实例a还未移动至m_thread之前,其所有的相关操作均处于当其被new时所处的线程,由于MainWindow在主线程中被构造,所以此时实例a位于主线程0x2a0c。此时通过任何方式,无论直接调用a_>show(),抑或是通过信号槽和invokeMethod调用show(),最终触发时show()必然在主线程0x2a0c之中。
  • 当实例a被移动至m_thread之后,其相关事件被托管,但是该实例本身仍位于主线程0x2a0c中,但是通过信号槽和invokeMethod调用show()时发生在m_thread线程0x3a9c之中。
  • 当m_timer与this被移动至m_thread之后,其所处线程仍不变为主线程0x2a0c,此时m_timer与this为同一线程,所以信号槽连接时使用DirectConnection,并且需要通过invokeMethod方式来调用m_timer的start方可在主线程0x2a0c中调用m_thread中的m_timer.start()函数(不然会报QTimer无法在另外线程打开的错误,这其实是QT对于QTimer机制的一种保护,有兴趣的读者可以研究下这里将QTimer进行moveToThread之后对于线程的限制保护机制),最后执行的mainShow()函数也确确实实的都在m_thread的线程0x3a9c中执行。

  3.结论总结

  • moveToThread方式并非“无脑”移动,是一种事件的托管;
  • 事件的产生只能通过信号槽或invokeMethod方式来实现,其他的方式(如直接调用)将会导致所调用函数在进行直接调用的线程之中执行,而非moveToThread之后的线程。
  • 由于QTimer操作对于线程的限制性,需要保证执行QTimer的相关函数时,如果想采用直接调用则需保证调用函数线程与QTimer所处线程一致,否则就必须使用信号槽或invokeMethod的方式来调用QTimer相关的函数。
  • “QT官方文档moveToThread的Warning”:moveToThread往往只能将当前对象从其当前所处线程“推”到某一线程中去(比如1中的常见情况,即在构造函数中将实例a从构建tempActive的线程“推”到了m_thread线程中),不能将当前对象从某线程中“拉”到该线程(即无法在实例a不处在的线程中执行对实例a的moveToThread操作,因为无法在另一线程控制a的自身线程)

你可能感兴趣的:(Qt学习,qt,多线程,moveToThread,run,QThread)