Qt对话框的事件循环分析(子线程中不能创建UI窗体分析)

重要:

GUI线程和辅助线程
如前所述,每个程序在启动时都有一个线程。这个线程被称为“主线程”(在Qt应用程序中也称为“GUI线程”)。Qt GUI必须在这个线程中运行。所有小部件和几个相关类(例如QPixmap)都不能在辅助线程中工作。辅助线程通常称为“工作线程”,因为它用于从主线程卸载处理工作。

首先,子线程不能创建与UI有关的对象,但是可以这样子做.只能在子线程中发一个信号到主线程中,由主线程创建对话窗口.子线程发完信号后,在子线程中while循环调用事件循环,.对话窗口退出之后,主线程调用接口,结束子线程的while循环!

  • 默认的线程在Qt中称为窗口线程,也叫主线程(UI线程),负责窗口事件处理或者窗口控件数据的更新
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情都要交给窗口线程处理
  • 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制。

/*************************************************

Qt只允许您在GUI线程中创建GUI元素

不能在子线程中操作UI,可以在子线程向主线程发送信号,主线程对应的槽函数来处理UI

其实Qt只是封装了Win32的Api,底层还是Win32的那套东西。
MFC/Win32一般情况下只有一个界面线程(可以理解为主线程,拥有消息队列),可以有N多工作线程。
而QThread应该指的是工作线程。
所以一个简单的QT-GUI程序,在Windows下应该是只有一个界面线程。

一、澄清概念

1.Qt主线程

Qt的主线程是唯一运行创建QApplication对象并调用exec()的线程,主要用于界面显示,因此又被称为GUI线程。

2.Qt子线程

Qt的子线程用于一些耗时操作,因此又被称为工作线程。

子线程不能用于直接刷新界面(QWidget不可重入,QObject可重入)。

若子线程企图修改界面控件,可通过线程间通信的方式:Qt的信号槽机制是跨线程的,因此可以用作线程间通信。

主线程是唯一允许创建QApplication或者QCoreApplication对象的,并且调用exec()。exec()启动了事件循环,一直在等待接收并且处理一个个Qt封装好的事件,比如鼠标移动事件,键盘按下事件等等。所以只有在主线程里你才可以方便利用各种Event去完成自己想要实现的需求。所以就限制你必须在主线程作UI相关操作。
除了规定,往根源说,再多的线程,实质上对于CPU来说,也是一件一件的处理,并不是我们凭空现象的同时处理。只是可以“智能”的处理一下当前迫切需要的数据,然后可以随时暂停,再去处理更加迫切的。如果2个线程同时处理UI显示,一个线程正在用于和用户交互更新显示,另外一个线程就只能是等待状态,并不能完成我们所期望的同时刷新UI的期望。
所以,基本上大部分应用开发框架都限制更新、创建UI必须在主线程里完成,而逻辑运算可以匹出新线程去完成。

t是一个以事件为驱动的框架。

QTUI在主线程,Qt所有的事件都在UI主线程, 包括定时事件。所有所有耗时的操作务必不要在事件中处理,否则影响整个的刷新的时间。。。。。

可以使用线程把去处理复杂的事件,然后把结果传到UI线程中

线程绘图

功能:子线程在处理函数中绘制图像,然后通过信号把绘制的图像传给主线程,主线程接收到图像之后调用update()函数更新绘图事件,进行图像的绘制。

也就是子线程把图片给画好了,传给主线程,主线程在窗口中绘制出来。主窗口中有一个按钮,按一次,绘制一次图像。

/*********************************************

1. 线程与界面组件需要注意的地方

  • 在QThread线程中不能直接创建QWidget之类的界面组件.
  • 因为在QT中,所有界面组件相关的操作都必须在主线程中(也就是GUI thread)
  • 所以, QThread线程不能直接操作界面组件.

2.QThread线程如何操作界面组件-方法1

  • 将多线程类对象封装为GUI界面类的类成员
  • 然后在子线程定义信号函数,通过信号槽机制,向界面组件emit发射信号,从而实现间接操作.

3.QThread线程如何操作界面组件-方法2

  • 使用QApplication::postEvent()实现向界面发送事件,从而能够封装一个自定义类

4.使用Invokes()函数来调用界面组件的信号槽-方法3

一般使用该函数(用来调用对方的私有信号或槽):

Qt对话框的事件循环分析(子线程中不能创建UI窗体分析)_第1张图片

该函数的连接方式默认使用的是Qt::AutoConnection

  • 表示如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

比如,当我们想调用一个obj下的compute(QString, int, double)槽函数时:

则只需要写入:

QMetaObject::invokeMethod(obj, "compute",
                            Q_ARG(QString, "sqrt"),                        
                            Q_ARG(int, 42),
                            Q_ARG(double, 9.7));

示例如下所示:

在Testtherd线程类里通过invokeMethod向父界面类的paintMsg槽函数发送信息

void Testtherd::run()
{
    int count=0;
    while(1)
    {
        QString str="请稍等,正在验证用户,登录中";
        for(int i =0;iparent(), "paintMsg",
                                            Q_ARG(QString, str));

        msleep(500);
    }
}

父界面类的paintMsg槽函数如下所示:

void loginwindow:: paintMsg(QString msg) {
this->LineHint->setText(msg);
}

运行效果如下:

 

Qt对话框的事件循环分析(子线程中不能创建UI窗体分析)_第2张图片

/*********************************************

写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习。首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供。主线程有个事件循环Event Loop,其实就是一个死循环在不断的等待你的消息队列,通过消息队列完成响应用户操作,绘图,以及相关操作。我们都知道QDialog有一个exec函数,这个函数会形成“模态”对话框,然后等待用户去输入OK还是Cancel,否则他绝不返回,如下

void test()
{
    QDialog dialog;
    dialog.exec();

    qDebug() << "finish exec";
}


我们可以看这个简单的例子,可以看到,当dialog被exec之后,我们的qDebug是不会输出的,除非我们人为去点了对话框的OK,否则,就会一直卡在exec之上。这个时候可能同学会有我一开始一样的误解,我们会误以为此时事件循环停止了,其实并不是停止,而是阻塞住了。为什么会阻塞?因为test这个函数没有返回嘛!

m_stateManager->postEvent(ev);
投递事件官方API也说的很清楚,会立即返回,所以别去担心此时投递的事件进入不到消息队列,真正要关心的是此时dialog的exec让你的主线程阻塞了,这个时候消息队列上的事件都不会进行操作,都在等待dialog的返回,只有dialog返回,接下来的事件才会依次进行。要记住,消息是可以正常投递的。

那么,有没有办法可以让dialog.exec()立即返回,同时我的对话框还在呢?方法是有的

void test()
{
    metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection);

    qDebug() << "finish exec";
}

Q_INVOKABLE void invokeTest()
{
    QDialog dialog;
    dialog.exec();
}


答案就是用Qt提供的元对象系统Meta Object System的invokeMethod,并且将第三个参数设为Qt:QueuedConnection。从字面上我们也可以看的出来,这个调用不会去立即调用,相反他是异步的,他会把这个函数投递到事件队列里,也就是说,这个例子中的qDebug会输出,输出之后,事件队列才会去调用dialog.exec()这个函数,当然了,调用这个函数之后又会达到我们一开始的那个阻塞的效果,你通过异步到最后的触发,始终你需要去面对exec给你当前事件循环造成阻塞的问题。

让我们再深入一点,当一个事件循环的时候还算简单。但是我们知道,Qt中对于状态机他是有一个异步的事件循环的,也就是说外面有事件循环,状态机本身也有事件循环。比如

m_stateManager->postEvent(ev);

m_tool->run();
你给状态机投递了一个事件,他根据状态迁移去调用你的tool,这一切看起来很美好,但如果你此时的tool是个跟之前一样的阻塞的exec呢?让我们来看一下。

void Tool::run()
{
    QDialog dialog;
    dialog.exec();
}
对于这个情况,当我们运行tool之后,我们的状态机就跟之前的主事件事件循环一样被阻塞了,也就是说如果我此时继续

m_stateManager->postEvent(ev2);
和之前一样,这个postEvent会立即返回,因为投递到事件队列都是立即返回的。但是关键的问题在于你的状态机整个事件循环都停止不动了,都在等你之前的tool运行结束,但因为你之前的tool是个dialog.exec()你必须手动去点OK,不然你的状态机事件循环就阻塞不动了,这个时候如果你的客户不断的去点你这个tool的event,那会产生噩梦般的效果----你点完OK之后又会来OK之后又会来OK。。。这其实就是你一旦点了OK,你的消息队列就又可以循环了,之前等待的ev就都会去执行了。而且要注意的就是,此时你的exec的执行在主线程上,只是不能进行返回,但还是可以接收诸如键盘,鼠标等事件投递。

前面也说了,事件循环和状态机循环是两个独立的循环,其实这也很好理解。如果没有事件循环,状态机事件怎么知道你有没有按下这个键?从而去投递给状态机呢?其实也就是说当你状态机事件阻塞的时候,你的主事件循环还在不断的接收你的键盘和鼠标的操作,这一点是没有影响的。

因此,要想实现在tool的时候我还能相应别的状态事件,其实做法也是一样的,就是

void Tool::run()
{
    metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection);
}

Q_INVOKABLE void invokeTest()
{
    QDialog dialog;
    dialog.exec();
}


立即返回,这个”立即返回“并不是说你的事情做完了,而是你更想让状态机能够进行之后事件的循环,别去因为你的dialog而耽误了大家。

最后说说模态这个主题,其实模态的理解就是你的消息队列都在正常进行,因为你不断的在等待,导致事件循环不能进行下去,必须你这边正常返回,你接下来的操作才能继续。

今天又重新思考了一下这个问题。同步的意思似乎就是必须要执行完成才能返回。异步的概念就是立即返回,之后执行,会把他扔到消息队列里,待同步函数处理完之后,然后去搜索事件队列进行操作。其实状态机归根结底就是一堆信号链接,只是他的方向是规矩状态迁移表来进行。作为主线程的Event Loop来说,当dialog进入exec的时候,就是就是在进行事件队列,并不是说他此时把事件队列给阻塞了,这个我之前理解有问题。exec的含义就是去处理事件队列,去处理事件循环,去检索当前还有哪些事件可以被处理,从而去正常处理。比如我们有一个主程序窗口MainWindow,有一个Dialog,此时我们去调用dialog的exec,内部会去创建一个QEventLoop,又因为这个dialog的所在线程和MainWindow在同一个线程上,所以看上去似乎是两个EventLoop,但实际上都是同一个线程的Event Loop(一个线程只能有一个Event Loop,这是原子性问题)。所以在dialog进行exec的时候他会去检索主线程上的事件队列去操作。

而我们之前讲的状态机,其实仔细想了想很简单,你就把他理解成是主程序总的Event Loop中的一个事件,他在进行操作的时候,不返回(tool去调用dialog的exec看上去似乎进行了事件循环在等待你新的event,但别忘了,你本身这个tool的run就是通过事件队列去触发的)所以必须要等待这个tool的exec返回,你的事件队列才能正常下去。

再次强调:

exec并不是说事件队列被你阻塞,而是才是让你进入一个真正等待处理事件队列的过程。
同一个线程只能有一个Event Loop,这个可以参考CP单核心单线程的处理逻辑。
在进行事件队列进行事件操作的时候,其实内部就是同步的方式在进行,必须等待函数全部执行完毕才能真正返回才能真正进行之后的event,这也可以说的通我们之前举的状态机的例子,看上去这个状态机引发了我们的tool,tool中调用了dialog的exec,看上去似乎很美好,在等待状态事件了。但此时你这个exec不返回,你如何让事件Event Loop继续进行下去。
同步函数就是必须要等待函数操作完成之后才能返回的函数。异步函数就是直接返回,他具体什么时候进行操作,待具体实现查看。(也可能是本线程的事件队列,也可能是别的线程进行run)。如果是别的线程进行run的时候,你可能会去想这个立即返回的问题,其实很简单,Qt的run都是start,其实就跟postEvent一样,只是简单的把他注册给线程管理器,由线程管理器再去跑他的run函数,那你本地的start当然立即返回了。

/******************************************************

什么是线程?
线程就是并行地做事情,就像进程一样。那么线程与进程有什么不同呢?当您在电子表格上进行计算时,可能还会有一个媒体播放器在同一台式机上运行,播放您最喜欢的歌曲。下面是两个并行工作进程的例子:一个运行电子表格程序;一个经营媒体播放器。多任务处理是一个众所周知的术语。仔细观察媒体播放器就会发现,在一个单一的过程中,也有一些事情是并行发生的。当媒体播放器将音乐发送到音频驱动程序时,用户界面的所有铃声和哨子都在不断更新。这就是线程的作用所在,单个进程中的并发性。

那么并发性是如何实现的呢?在单核cpu上并行工作是一种错觉,有点类似于电影中移动图像的错觉。对于进程,这种错觉是通过在很短的时间后中断处理器在一个进程上的工作而产生的。然后处理器转到下一道工序。为了在进程之间切换,保存当前的程序计数器并加载下一个处理器的程序计数器。这是不够的,因为同样需要对寄存器、某些体系结构和操作系统特定的数据进行处理。

正如一个CPU可以驱动两个或多个进程一样,也可以让CPU运行在一个进程的两个不同代码段上。当一个进程启动时,它总是执行一个代码段,因此这个进程被称为有一个线程。然而,程序可能决定启动第二个线程。然后,在一个进程内同时处理两个不同的码序列。通过重复保存程序计数器和寄存器,然后加载下一个线程的程序计数器和寄存器,在单核cpu上实现并发。在活动线程之间循环不需要程序的合作。当切换到下一个线程时,一个线程可能处于任何状态。

当前CPU设计的趋势是多核并存。典型的单线程应用程序只能使用一个内核。然而,一个具有多个线程的程序可以被分配到多个核上,使事情以真正并发的方式发生。因此,将工作分配给多个线程可以使程序在多核cpu上运行得更快,因为可以使用额外的核。

GUI线程和辅助线程
如前所述,每个程序在启动时都有一个线程。这个线程被称为“主线程”(在Qt应用程序中也称为“GUI线程”)。Qt GUI必须在这个线程中运行。所有小部件和几个相关类(例如QPixmap)都不能在辅助线程中工作。辅助线程通常称为“工作线程”,因为它用于从主线程卸载处理工作。

同时存取数据
每个线程都有自己的堆栈,这意味着每个线程都有自己的调用历史和局部变量。与进程不同,线程共享相同的地址空间。下图显示了线程的构建块是如何位于内存中的。非活动线程的程序计数器和寄存器通常保存在内核空间中。每个线程都有一个共享的代码副本和一个单独的堆栈。


如果两个线程有一个指向同一对象的指针,那么两个线程可能会同时访问该对象,这可能会破坏对象的完整性。很容易想象,当同一个对象的两个方法同时执行时,可能会出现许多问题。

有时需要从不同的线程访问一个对象;例如,生活在不同线程中的对象需要通信时。由于线程使用相同的地址空间,因此线程交换数据比进程交换数据更容易、更快。数据不必序列化和复制。传递指针是可能的,但必须严格协调哪个线程接触哪个对象。必须防止同时在一个对象上执行操作。有几种方法可以实现这一点,下面将介绍其中一些方法。

那么什么是安全的呢?在一个线程中创建的所有对象都可以在该线程中安全地使用,前提是其他线程没有对它们的引用,并且对象与其他线程没有隐式耦合。这种隐式耦合可能发生在实例之间共享数据时,比如静态成员、单例或全局数据。熟悉线程安全类和可重入类和函数的概念。

使用线程
线程基本上有两个用例:

利用多核处理器提高处理速度。
通过卸载长时间持续的处理或阻塞对其他线程的调用来保持GUI线程或其他时间关键线程的响应。
何时使用线程的替代方案
开发人员需要非常小心线程。启动其他线程很容易,但很难确保所有共享数据保持一致。问题通常很难发现,因为它们可能只是偶尔出现,或者只在特定的硬件配置上出现。在创建线程来解决某些问题之前,应该考虑可能的替代方案。

供选择的    注释
QEventLoop::processEvents()    在耗时的计算过程中反复调用QEventLoop::processEvents()可以防止GUI阻塞。但是,这个解决方案不能很好地扩展,因为根据硬件的不同,对processEvents()的调用可能发生得太频繁,或者不够频繁
QTimer    后台处理有时可以方便地使用一个计时器来调度一个插槽在未来的某个时间点的执行。间隔为0的计时器一旦不再有需要处理的事件就会超时。
QSocketNotifier QNetworkAccessManager QIODevice::readyRead()    这是一种替代使用一个或多个线程(每个线程在慢速网络连接上阻塞读取)的方法。只要可以快速执行响应大量网络数据的计算,这种响应式设计就比线程中的同步等待更好。与线程设计相比,反应式设计更不容易出错,而且节能。在许多情况下也有性能优势。
一般来说,建议只使用安全且经过测试的路径,并避免引入特殊线程概念。
QtConcurrent模块提供了一个简单的接口,可以将工作分配到所有处理器的核心上。线程代码完全隐藏在QtConcurrent框架中,所以您不需要考虑细节。然而,当需要与正在运行的线程通信时,QtConcurrent不能被使用,它不应该被用于处理阻塞操作。

应该使用哪种 Qt 线程技术?
参考:

Qt 线程基础知识
以下各节介绍 QObjects 如何与线程交互,程序如何安全地访问来自多个线程的数据,以及异步执行如何在不阻塞线程的情况下生成结果。

QObject和线程
如上所述,开发人员在从其他线程调用对象的方法时必须始终小心谨慎。线程相关性不会更改这种情况。
Qt文档将几个方法标记为线程安全的。postEvent()是一个值得注意的例子。线程安全的方法可以从不同的线程同时调用。

在通常没有并发访问方法的情况下,在发生并发访问之前,调用其他线程中对象的非线程安全方法可能会工作数千次,从而导致意外的行为。编写测试代码并不能完全确保线程的正确性,但这仍然很重要。在Linux上,Valgrind和Helgrind可以帮助检测线程错误。

保护数据的完整性
在编写多线程应用程序时,必须格外小心,以避免数据损坏。有关如何安全地使用线程的讨论,参阅同步线程。

处理异步执行
获取辅助线程结果的一个方法就是等待线程终止。但是,在许多情况下,阻止等待是不能接受的。阻塞等待的另一种选择是异步的结果交付,使用的是已发布的事件或排队的信号和槽。这将产生一定的开销,因为操作的结果不会出现在下一个源代码行中,而是出现在源文件中其他位置的一个槽中。Qt开发人员习惯于处理这种异步行为,因为它与GUI应用程序中使用的事件驱动编程非常相似。

/********************************************

一、关于线程
线程的用途:单个进程内的并发。

1.1、单核CPU
在单核 CPU 上并行工作是一种错觉。对于进程,这种错觉是通过在很短的时间后中断处理器对一个进程的工作而产生的。然后处理器继续下一个过程。为了在进程之间切换,保存当前程序计数器并加载下一个处理器的程序计数器;对寄存器和某些体系结构和操作系统特定数据进行处理。

正如一个 CPU 可以驱动两个或多个进程一样,也可以让 CPU 在一个进程的两个不同代码段上运行。当一个进程启动时,它总是执行一个代码段,该进程被称为主线程。程序可能会决定启动第二个线程。然后,在一个进程中同时处理两个不同的代码序列。在单核 CPU 上通过重复保存程序计数器和寄存器然后加载下一个线程的程序计数器和寄存器来实现并发。在活动线程之间循环不需要程序的合作。当切换到下一个线程时,一个线程可能处于任何状态。

1.2、多核CPU
CPU 设计的当前趋势是具有多个内核。具有多个线程的程序可以分配给多个内核,从而使事情以真正并发的方式发生。因此,将工作分配给多个线程可以使程序在多核 CPU 上运行得更快。

1.3、GUI 线程和工作线程
Qt GUI 必须在主线程中运行。所有QWidget和几个相关的类,例如 QPixmap,在辅助线程中不起作用。辅助线程通常被称为“工作线程”。

1.4、同时访问数据
每个线程都有自己的堆栈,这意味着每个线程都有自己的调用历史和局部变量。与进程不同,线程共享相同的地址空间。下图显示了线程的构建块如何在内存中定位。非活动线程的程序计数器和寄存器通常保存在内核空间中。每个线程都有一个共享的代码副本和一个单独的堆栈。

如果两个线程具有指向同一个对象的指针,则两个线程可能会同时访问该对象,这可能会破坏对象的完整性。很容易想象当同一个对象的两个方法同时执行时会出现很多问题。

有时需要从不同的线程访问一个对象。例如,当存在于不同线程中的对象需要通信时。由于线程使用相同的地址空间,线程交换数据比进程更容易便捷。数据不必序列化和复制。可以传递指针,但必须严格协调哪个线程接触哪个对象。必须防止对一个对象同时执行操作。

二、使用线程
线程基本上有两种使用场景:

需要使用多核处理器加快处理速度。
需要处理耗时操作但要保持 GUI 线程。
2.1、线程的替代方案
开发人员需要非常小心使用线程。启动线程很容易,但很难确保所有共享数据保持一致。问题通常很难发现,因为它们可能只偶尔出现一次或仅在特定的硬件配置上出现。在使用解决某些问题之前,应该考虑可能的替代方案。

QEventLoop::processEvents():在耗时计算期间重复调用事件循环处理程序可防止 GUI 阻塞。但是,此解决方案不能很好地扩展,因为对 processEvents() 的调用可能发生得太频繁,也可能不够频繁,具体取决于硬件。
QTimer:有时可以使用计时器方便地完成后台处理。
QSocketNotifier、
QNetworkAccessManager、QIODevice::readyRead():(网络操作)这是拥有一个或多个线程的替代方案,每个线程在慢速网络连接上阻塞读取。只要响应一大块网络数据的计算可以快速执行,这种反应式设计比线程中的同步等待更好。响应式设计比线程更不容易出错。在许多情况下,还有性能优势。
QtConcurrent 模块提供了一个简单的接口,用于将工作分配给所有处理器的内核。线程代码完全隐藏在 QtConcurrent 框架中,因此不必关心细节。但是,当需要与正在运行的线程进行通信时,不能使用 QtConcurrent,也不应该使用它来处理阻塞操作。

三、类列表
QAtomicInteger:Qt提供的原子操作
QAtomicPointer:Qt提供的原子操作
QFuture:异步运行结果
QFutureSynchronizer:简化了QFuture的同步
QFutureWatcher:监视QFuture
QMutex:互斥量,使一段代码在一段时间内只能由一个线程访问
QMutexLocker:互斥锁,关联QMutex对象。创建时锁定、销毁时解锁QMutex对象
QReadLocker:读取锁,用作简化 QReadWriteLock 的读取
QReadWriteLock:读写锁
QRecursiveMutex:互斥量,同一线程内可以多次调用 lock()
QRunnable:线程池中的线程要执行的内容
QSemaphore:信号量
QSemaphoreReleaser:确保安全释放信号量
QThread:管控一个线程
QThreadPool:线程池
QThreadStorage:一个模板类,可以为每个线程储存指定类型的数据
QWaitCondition:线程等待条件
QWriteLocker:写入锁,用作简化 QReadWriteLock 的写入
QtConcurrent:Qt并发命名空间
四、Qt中的多线程技术
Qt 提供了许多用于处理线程的类和函数。下面是 Qt 程序员可以用来实现多线程应用程序的四种不同方法。

4.1、QThread:具有可选事件循环的低级 API
QThread 是 Qt 中所有线程控制的基础。 每个 QThread 实例代表并控制一个线程。

QThread 可以直接实例化或子类化。实例化 QThread 提供了一个并行事件循环,允许在辅助线程中调用 QObject 槽函数。子类化 QThread 允许应用程序在启动其事件循环之前初始化新线程,或者在没有事件循环的情况下运行并行代码。

4.2、QThreadPool 和 QRunnable:重用线程
频繁地创建和销毁线程可能会很昂贵。为了减少这种开销,可以将现有线程重用于新任务。QThreadPool 是可重用 QThread 的集合。

要在 QThreadPool 的线程之一中运行代码,请重新实现 QRunnable::run() 并实例化子类 QRunnable。使用 QThreadPool::start() 将 QRunnable 放入 QThreadPool 的运行队列中。当一个线程可用时, QRunnable::run() 中的代码将在该线程中执行。

每个 Qt 应用程序都有一个全局线程池,可以通过 QThreadPool::globalInstance() 访问。 这个全局线程池会根据 CPU 中的内核数自动维护最佳线程数。 但是,可以显式创建和管理单独的 QThreadPool。

4.3、Qt Concurrent:使用高级 API
Qt Concurrent 模块提供了处理一些常见并行计算模式的高级函数:map、filter 和 reduce。与使用 QThread 和 QRunnable 不同,这些函数从不需要使用低级线程原语,例如互斥体或信号量。相反,它们返回一个 QFuture 对象,该对象可用于在函数准备好时检索函数的结果。QFuture 还可用于查询计算进度和暂停/恢复/取消计算。为方便起见,QFutureWatcher 支持通过信号和槽与 QFuture 进行交互。

Qt Concurrent 的映射、过滤和缩减算法会自动在所有可用的处理器内核之间分配计算。

该模块还提供了 QtConcurrent::run() 函数,它可以在另一个线程中运行任何函数。但是,QtConcurrent::run() 仅支持 map、filter 和 reduce 函数可用的功能子集。QFuture 可用于检索函数的返回值并检查线程是否正在运行。但是,对 QtConcurrent::run() 的调用仅使用一个线程,无法暂停/恢复/取消,也无法查询进度。

4.4、WorkerScript:QML 中的线程
WorkerScript QML 类型允许 JavaScript 代码与 GUI 线程并行运行。

每个 WorkerScript 实例都可以附加一个 .js 脚本。当 WorkerScript.sendMessage() 被调用时,脚本将在单独的线程(和单独的 QML 上下文)中运行。当脚本完成运行时,它可以将回复发送回 GUI 线程,该线程将调用 WorkerScript.onMessage() 信号处理程序。

使用 WorkerScript 类似于使用已移动到另一个线程的 QObject。数据通过信号在线程之间传输。

4.5、以上各方法比较
特点

QThread

QRunnable 和 QThreadPool

QtConcurrent::run()

Qt Concurrent

(Map, Filter, Reduce)

WorkerScript

语言

C++

C++

C++

C++

QML

可以指定线程优先级

Yes

Yes

可以运行事件循环

Yes

可以通过信号接收数据更新

Yes(QObject接收信号) 

Yes(WorkerScript 接收信号)

可以使用信号控制

Yes(QThread接收信号)

Yes(QFutureWatcher 接收信号)

可以通过 QFuture 监控

部分

Yes

内置暂停/恢复/取消功能

Yes

4.6、使用示例
线程的生命周期    操作    解决方案
一次性    在另一个线程中运行一个新的线性函数,可在运行期间更新进度    Qt 提供了不同的解决方案:
将该函数放在 QThread::run() 的重新实现中并启动 QThread。 发出信号以更新进度。
将该函数放在 QRunnable::run() 的重新实现中,并将 QRunnable 添加到 QThreadPool。 写入线程安全变量以更新进度。
使用 QtConcurrent::run() 运行该函数。写入线程安全变量以更新进度
一次性    在另一个线程中运行现有函数并获取其返回值    使用 QtConcurrent::run() 运行该函数。当函数返回时,让 QFutureWatcher 发出 finished() 信号,并调用 QFutureWatcher::result() 来获取函数的返回值。
一次性    使用所有可用内核对容器的所有项目执行操作。例如,从图像列表中生成缩略图    使用 Qt Concurrent 的 QtConcurrent::filter() 函数选择容器元素,使用QtConcurrent::map() 函数对每个元素应用操作。要将输出折叠成单个结果,请改用 QtConcurrent::filteredReduced() 和 QtConcurrent::mappedReduced()。
一次性/永久    在纯 QML 应用程序中执行长时间计算,并在结果准备好后更新GUI。    将计算代码放在 .js 脚本中并将其附加到 WorkerScript 实例。调用WorkerScript.sendMessage() 以在新线程中开始计算。 让脚本也调用 sendMessage(),将结果传回 GUI 线程。 在 onMessage 中处理结果并在其中更新 GUI。
永久    有一个对象存在于另一个线程中,该线程可以根据请求执行不同的任务和/或可以接收新数据以进行处理    将 QObject 子类化以创建worker对象。实例化这个worker对象和一个 QThread。将工作线程移动到新线程。通过队列方式连接的信号槽向worker对象发送命令或数据。
永久    在另一个线程中重复执行昂贵的操作,该线程不需要接收任何信号或事件    直接在 QThread::run() 的重新实现中编写无限循环。在没有事件循环的情况下启动线程。让线程发出信号以将数据发送回 GUI 线程。
五、同步线程
虽然线程的目的是让代码并行运行,但有时线程必须停止并等待其他线程。例如,如果两个线程同时尝试写入同一个变量,结果是不确定的。Qt 提供了用于同步线程的低级原语和高级机制。 

5.1、低级同步原语
QMutex 是强制互斥量的基本类。一个线程锁定一个互斥量以获取对共享资源的访问。如果第二个线程在互斥已经被锁定的情况下试图锁定它,第二个线程将被置于睡眠状态,直到第一个线程完成其任务并解锁互斥锁。

QReadWriteLock 与 QMutex 类似,只是它区分了“读”和“写”访问。当一块数据没有被写入时,多个线程同时读取是安全的。QReadWriteLock 允许同时读取共享数据,从而提高并行性。

QSemaphore 是 QMutex 的泛化,它保护一定数量的相同资源。相比之下,QMutex 只保护一个资源。

QWaitCondition 不是通过强制互斥而是通过提供条件变量来同步线程。虽然其他原语使线程等待资源解锁,但 QWaitCondition 使线程等待直到满足特定条件。要允许等待的线程继续进行,请调用wakeOne() 唤醒一个随机选择的线程或wakeAll () 同时唤醒它们。

5.1.1、风险

如果一个线程锁定了一个资源但没有解锁它,应用程序可能会冻结,因为其他线程将永久无法使用该资源。例如,如果抛出异常并强制当前函数返回而不释放其锁,就会发生这种情况。

另一个类似的场景是死锁,例如,假设线程 A 正在等待线程 B 解锁资源,如果线程 B 也在等待线程 A 解锁不同的资源,那么两个线程将永远等待,因此应用程序将冻结。

5.1.2、便利类

QMutexLocker、QReadLocker 、 QWriteLocker 是便利类,它们使 QMutex 和 QReadWriteLock 的使用更容易。它们在构造时锁定资源,并在销毁时自动解锁。它们旨在简化使用 QMutex 和 QReadWriteLock 的代码,从而减少资源被意外永久锁定的可能性。

5.2、高级事件队列 
Qt 的事件系统对于线程间通信非常有用。每个线程可能有自己的事件循环。要调用另一个线程中的槽函数(或任何可调用方法),请将调用放在目标线程的事件循环中。这让目标线程在槽开始运行之前完成其当前任务,而原始线程继续并行运行。

要将调用置于事件循环中,请建立一个Qt::QueuedConnection类型的信号槽连接。每当发出信号时,其参数将被事件系统记录。信号接收者所在的线程将运行该槽。或者,调用QMetaObject::invokeMethod() 不用信号也能达到同样的效果,两种情况都必须使用Qt::QueuedConnection连接,因为直接连接绕过了事件系统,会在当前线程中立即运行该方法。
与使用低级原语不同,使用事件系统进行线程同步时没有死锁的风险。但是,事件系统不强制互斥。如果可调用方法访问共享数据,它们仍然必须用低级原语保护。

话虽如此,Qt 的事件系统,连同隐式共享的数据结构,提供了传统线程锁定的替代方案。如果信号和槽被独占使用,并且线程之间不共享变量,则多线程程序可以完全没有低级原语。

六、可重入和线程安全的概念
6.1、可重入函数和线程安全函数
可重入函数:函数可以同时被多个线程调用,但是每个调用者只能使用自己的数据,而不能使用共享数据。
线程安全函数:函数可以同时被多个线程调用,调用者可以使用共享数据,共享数据的使用是串行的,即一个线程使用时完全占用共享数据,用完了再由第二个线程完全占用使用。
6.2、可重入类和线程安全类
可重入类,它的成员函数可以被多个线程安全地调用,只要每个线程使用这个类的不同的对象。
线程安全类,它的成员函数能够被多线程安全地调用,即使所有的线程都使用该类的同一个实例也没有关系。
因此,线程安全函数总是可重入的,但可重入函数并不总是线程安全的。

七、QObject和线程
QThread 继承了 QObject,它发出信号来指示线程开始或完成执行。QObjects 可以在多个线程中使用,发出调用其他线程中的槽的信号。

7.1、QObject 重入
QObject 是可重入的。它的大多数非 GUI 子类,例如 QTimer、QTcpSocket、QUdpSocket 和 QProcess 也是可重入的,使得可以同时从多个线程使用这些类。注意这些类是设计为从创建和使用在单个线程中。

在一个线程中创建对象并从另一个线程调用其函数不能保证工作。需要注意三个约束:

QObject 的子对象必须始终在创建父对象的线程中创建。
事件驱动的对象只能在单线程中使用,具体来说,这适用于定时器机制和网络模块。如定时器或Socket不能在线程A创建而在线程B中启动。
必须确保在删除 QThread 之前删除在线程中创建的所有对象。
尽管 QObject 是可重入的,但 GUI 类,尤其是 QWidget 及其所有子类是不可重入的。它们只能在主线程中使用。QCoreApplication::exec() 也必须从该线程调用。

通常,不支持在 QApplication 之前创建 QObjects,因为可能导致退出时奇怪的崩溃,具体取决于平台。这意味着也不支持 QObject 的静态实例。结构合理的单线程或多线程应用程序应该使 QApplication 成为第一个创建,最后一个销毁 QObject。

7.2、线程的事件循环
每个线程都可以有自己的事件循环。初始线程使用 QCoreApplication::exec() 启动其事件循环,或者对于单对话框 GUI 应用程序,有时使用 QDialog::exec()。其他线程可以使用 QThread::exec() 启动事件循环。和QCoreApplication一样,QThread提供了一个exit(int)函数和一个quit()函数。

QObject 对象存在于创建它的线程中。该对象的事件由该线程的事件循环调度。使用 QObject::thread() 可以获取QObject 所在的线程。

QObject::moveToThread() 函数更改对象及其子对象的线程(如果对象有父对象,则不能移动对象)。

在拥有该对象的线程以外的线程调用 QObject 上的 delete 是不安全的,除非保证该对象在那一刻不处理事件。使用 QObject :: deleteLater ()则可以安全删除对象,此函数会发布一个 DeferredDelete 事件,该对象的线程的事件循环最终会在处理池事件时删除对象。

可以随时使用线程安全函数 QCoreApplication::postEvent() 手动将事件发布到任何线程中的任何对象。事件将由创建该对象的线程的事件循环自动调度。

所有线程都支持事件过滤器,限制是监控对象必须和被监控对象在同一个线程中。类似的,QCoreApplication::sendEvent()只能用于将事件分派给在调用函数的线程中存活的对象。

7.3、从其他线程访问 QObject 子类
QObject 及其所有子类都不是线程安全的。整个事件传递系统也不是线程安全的。

如果您在不存在于当前线程中的 QObject 子类上调用函数并且该对象可能接收事件,则必须使用互斥锁保护对 QObject 子类内部数据的所有访问,否则,可能会遇到崩溃或其他不希望的情况行为。

与其他对象一样,QThread 对象存在于创建对象的线程中,而不是在调用 QThread::run() 时创建的线程中。在 QThread 子类中提供槽函数通常是不安全的,除非保护带有互斥锁的成员变量。

另一方面,可以安全地从 QThread::run () 实现中发出信号,因为信号发出是线程安全的。

7.4、跨线程的信号和槽
信号和槽

QObject::connect() 本身是线程安全的。

八、Qt 模块中的线程支持
8.1、线程和 SQL 模块
连接只能在创建它的线程内使用。不支持在线程之间移动连接或从不同线程创建查询。

8.2、线程中绘制
QPainter 可以在线程中用于在 QImage、QPrinter 、QPicture 绘画设备上绘画。不支持在 QPixmaps 和 QWidgets 上绘画。

在给定的绘制设备上一次只能有一个线程绘制。

8,3、线程和富文本处理
QTextDocument、QTextCursor 和所有相关的类都是可重入的。

8.4、线程和 SVG 模块
QtSvg 模块中的 QSvgGenerator 和 QSvgRenderer 类是可重入的。

8.5、线程和隐式共享类
Qt 对其许多值类使用称为隐式共享的优化,特别是 QImage 和 QString。隐式共享类可以安全地跨线程复制。它们是完全可重入的。

在很多人的印象中,隐式共享和多线程是不兼容的概念,因为引用计数通常是这样做的,但是 Qt 使用原子引用计数来确保共享数据的完整性,避免引用计数器的潜在损坏。

请注意,原子引用计数不保证线程安全。在线程之间共享隐式共享类的实例时应使用适当的锁定。这与所有可重入类的要求相同。但是,建议使用信号和槽在线程之间传递数据,因为这无需任何显式锁定即可完成。

隐式共享类确实是隐式共享的,即使在多线程应用程序中,也可以安全地使用它们,就好像它们是普通的、非共享的、可重入的基于值的类一样。

/**********************************************************

GUI线程
Qt应用程序exec后就会生成一个线程,这个线程就是主线程,在GUI程序中也称为GUI线程。主线程也是唯一允许创建QApplication或QCoreAppliation对象,比并且可以对创建的对象调用exec()的线程,从而进入事件循环。

在只有主线程即单线程的情况中,每一个事件的发生都需要进入事件循环进行等待,如有在某一步计算量比较大,则会一直占用CPU不放,导致其它操作无法完成,界面陷入冻结状态
所以,对于计算量大的操作,需要放到一个单独的线程进行计算,然后通过信号槽的方式和GUI线程进行通信。

QT多线程的实现方式
1. 重写QThread的run()
实现方法:
新建一个类,继承QThread,重写虚函数run();

  class WorkerThread : public QThread
  {
      Q_OBJECT
      void run() override {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }
  signals:
      void resultReady(const QString &s);
  };
 
  void MyObject::startWorkInAThread()
  {
      WorkerThread *workerThread = new WorkerThread(this);
      connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
      connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
      workerThread->start();
  }

优点
可以通过信号槽与外界通信
缺点
每次新建一个线程都需要继承QThread,实现一个新类,使用不太方便。
要自己进行内存的管理(线程的释放和删除);频繁的创建和释放会给系统带来比较大的开销。
适用场景
QThread适用于那些常驻内存的任务

2. QThread的moveToThread**
实现方法
创建一个集成QObject的类(myObject),new 一个QThread,并调用moveToThread(),将创建和的myObject类移动到子线程中,子线程(myObject)通过发发送信号,利用信号槽机制,与主线程进行通信。

 
  class Worker : public QObject
  {
      Q_OBJECT
 
  public slots:
      void doWork(const QString ¶meter) {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }
 
  signals:
      void resultReady(const QString &result);
  };
 
  class Controller : public QObject
  {
      Q_OBJECT
      QThread workerThread;
  public:
      Controller() {
          Worker *worker = new Worker;
          worker->moveToThread(&workerThread);
          connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
          connect(this, &Controller::operate, worker, &Worker::doWork);
          connect(worker, &Worker::resultReady, this, &Controller::handleResults);
          workerThread.start();
      }
      ~Controller() {
          workerThread.quit();
          workerThread.wait();
      }
  public slots:
      void handleResults(const QString &);
  signals:
      void operate(const QString &);
  };

优点
1、相对重写QThread::run()函数的方法更加灵活:

moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,movetothread给我们编写代码提供了新的思路,当然不是说子类化qthread不好,只是你应该知道还有这种方式去调用线程。

轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得movetothread和子类化QThread的区别不大,更可能是使用习惯引导。又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveThread的好处就来了,你可以把这段代码的从属着movetothread,把代码移到槽函数,用信号触发它就行了。其它的话movetothread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而movetothread就有很多触发的入口。

3. QRunnalble的run**
Qrunnable是所有可执行对象的基类。我们可以继承Qrunnable,并重写虚函数
实现方法
1、继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable。
2、重写run函数。还是和QThread一样,需要重写run函数,run是一个纯虚函数,必须重写。
3、使用QThreadPool启动线程

class Runnable:public QRunnable
{
       //Q_OBJECT   注意了,Qrunnable不是QObject的子类。
public:
       Runnable();
       ~Runnable();
       void run();
};
 
 
Runnable::Runnable():QRunnable()
{
 
}
 
Runnable::~Runnable()
{
       cout<<"~Runnable()"< }
 
void Runnable::run()
{
       cout<<"Runnable::run()thread :"<        cout<<"dosomething ...."< }
int main(int argc, char *argv[])
{
       QCoreApplication a(argc, argv);
       cout<<"mainthread :"<        Runnable runObj;
       QThreadPool::globalInstance()->start(&runObj);
       returna.exec();
}

优点:
无需手动释放资源,QThreadPool启动线程执行完成后会自动释放。
缺点:
不能使用信号槽与外界通信。
适用场景:
QRunnable适用于线程任务量比较大,需要频繁创建线程。QRunnable能有效减少内存开销。
和QThread的区别

与外界通信方式不同。由于QThread是继承于QObject的,但QRunnable不是,所以在QThread线程中,可以直接将线程中执行的结果通过信号的方式发到主程序,而QRunnable线程不能用信号槽,只能通过别的方式,等下会介绍。

启动线程方式不同。QThread线程可以直接调用start()函数启动,而QRunnable线程需要借助QThreadPool进行启动。

资源管理不同。QThread线程对象需要手动去管理删除和释放,而QRunnable则会在QThreadPool调用完成后自动释放。

Qt线程之QRunnable的使用详解

4. QtConcurrent的run**
Concurrent是并发的意思,QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展。

QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。详见前面的文章介绍,这里不再赘述。

需要注意的是,由于该线程取自全局线程池QThreadPool,函数不能立马执行,需要等待线程可用时才会运行。
实现方法
1、首先在.pro文件中加上以下内容:QT += concurrent

2、包含头文件#include ,然后就可以使用QtConcurrent了

QFuture fut1 = QtConcurrent::run(func, QString(“Thread 1”)); fut1.waitForFinished();

#include
#include
#include
#include
#include
#include
#include
#include"XxwImgOp.h"
#ifdef _DEBUG
#pragma comment(lib,".\\XxwImgOpd.lib")
#else
#pragma comment(lib,".\\XxwImgOp.lib")
#endif // _DEBUG
 
using namespace QtConcurrent;
 
XxwImgOp xxwImgOp;
cv::Mat src = cv::imread("1.bmp", 0);
cv::Mat  dst, dst1, dst2;
 
void hello(cv::Mat src)
{
    qDebug() << "-----------" << QTime::currentTime()<<"------------------------"<     xxwImgOp.fManualThreshold(src, dst, 50, 150);
    qDebug() <<"************" << QTime::currentTime() <<"**********************"<< QThread::currentThreadId();
 
}
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QFuture f1 = run(hello,  src);
    QFuture f2 = run(hello, src);
    //阻塞调用,阻塞主线程直到计算完成
    f1.waitForFinished();
    f2.waitForFinished();
 
    //阻塞为End的执行顺序
    qDebug() << "End";
    return a.exec();
}

特点:

//调用外部函数 QFuture f1 =QtConcurrent::run(func,QString(“aaa”));

//调用类成员函数 QFuture f2 =QtConcurrent::run(this,&MainWindow::myFunc,QString(“bbb”));

要为其指定线程池,可以将线程池的指针作为第一个参数传递进去

向该函数传递参数,需要传递的参数,则跟在函数名之后

可以用run函数的返回值funIr来控制线程。
如: funIr.waitForFinished(); 等待线程结束,实现阻塞。
funIr.isFinished() 判断线程是否结束
funIr, isRunning() 判断线程是否在运行
funIr的类型必须和线程函数的返回值类型相同,可以通过
funIr.result() 取出线程函数的返回值

缺点
不能直接用信号和槽函数来操作线程函数,eg : 当线程函数结束时,不会触发任何信号。

多线程间的通信
方法一
将多线程类对象封装为GUI界面类的类成员
然后在子线程定义信号函数,通过信号槽机制,向界面组件emit发射信号,从而实现间接操作.

方法二
使用QApplication::postEvent()实现向界面发送事件,从而能够封装一个自定义类

方法三
使用Invokes()函数来调用界面组件的信号槽

一般使用该函数(用来调用对方的私有信号或槽):

该函数的连接方式默认使用的是Qt::AutoConnection
表示如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
比如,当我们想调用一个obj下的compute(QString, int, double)槽函数时:
则只需要写入:

QMetaObject::invokeMethod(obj, "compute",
                            Q_ARG(QString, "sqrt"),                        
                            Q_ARG(int, 42),
                            Q_ARG(double, 9.7));

注意
在QThread线程中不能直接创建QWidget之类的界面组件.
因为在QT中,所有界面组件相关的操作都必须在主线程中(也就是GUI thread)
所以, QThread线程不能直接操作界面组件.

易犯错误

1、子线程中操作UI

Qt创建的子线程中是不能对UI对象进行任何操作的,即QWidget及其派生类对象,这个是我掉的第一个坑。可能是由于考虑到安全性的问题,所以Qt中子线程不能执行任何关于界面的处理,包括消息框的弹出。正确的操作应该是通过信号槽,将一些参数传递给主线程,让主线程(也就是Controller)去处理。

2、信号的参数问题

元对象系统即是提供了Qt类对象之间的信号槽机制的系统。要使用信号槽机制,类必须继承自QObject类,并在私有声明区域声明Q_OBJECT宏。当一个cpp文件中的类声明带有这个宏,就会有一个叫moc工具的玩意创建另一个以moc开头的cpp源文件(在debug目录下),其中包含了为每一个类生成的元对象代码。
在使用connect函数的时候,我们一般会把最后一个参数忽略掉

我们一般会用到方式是有三种:

* 自动连接(AutoConnection),默认的连接方式。如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;如果发送者与接受者处在不同线程,等同于队列连接。


直接连接(DirectConnection)。当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
*
队列连接(QueuedConnection)。当控制权回到接受者所在线程的事件循环时,槽函数被调用。这时候需要将信号的参数塞到信号队列里。槽函数在接受者所在线程执行。

signals:
    //自定义发送的信号
    void myThreadSignal(const int, string, string, string, string);

貌似没什么问题,然而实际运行起来槽函数根本就没有被调用,程序没有崩溃,VS也没报错。在查阅了N多博客和资料中才发现,在线程间进行信号槽连接时,参数不能随便写。
为什么呢?我的后四个参数是标准库中的string类型,这不是元对象系统内置的类型,也不是c++的基本类型,系统无法识别,然后就没有进入信号槽队列中了,自然就会出现问题。解决方法有三种,最简单的就是使用Qt的数据类型了

第二种方法就是往元对象系统里注册这个类型。注意,在qRegisterMetaType函数被调用时,这个类型应该要确保已经被完好地定义了。

qRegisterMetaType("MyClass");

方法三是改变信号槽的连接方式,将默认的队列连接方式改为直接连接方式,这样的话信号的参数直接进入槽函数中被使用,槽函数立刻调用,不会进入信号槽队列中。但这种方式官方认为有风险,不建议使用。

connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::DirectConnection)

还有几点需要注意

一定要用信号槽机制,别想着直接调用,你会发现并没有在子线程中执行。
自定义的类不能指定父对象,因为moveToThread函数会将线程对象指定为自定义的类的父对象,当自定义的类对象已经有了父对象,就会报错。
当一个变量需要在多个线程间进行访问时,最好加上voliate关键字,以免读取到的是旧的值。当然,Qt中提供了线程同步的支持,比如互斥锁之类的玩意,使用这些方式来访问变量会更加安全。
 

你可能感兴趣的:(Qt事件机制,QT-UI,后端)