力学笃行(四)Qt 线程与信号槽

线程与信号槽

  • 1. 主窗口(MainWindow)主线程
  • 2. 线程
    • 2.1 QThread
    • 2.2 QtConcurrent::run()
    • 2.3 thread 的调用方式
  • 3. 信号槽
    • 3.1 connect
    • 3.2 元对象系统中注册自定义数据类型
  • 附录一 信号槽机制与主线程进行通信示例

1. 主窗口(MainWindow)主线程

在Qt中,线程和信号槽机制是两个核心概念,它们结合使用可以实现多线程编程,并在不同线程之间进行通信。

这里提一个主线程的概念,主窗口(MainWindow)通常是应用程序的主要界面,它的生命周期和事件循环是由主线程管理的。虽然可以在主窗口的代码中创建和操作其他线程,但通常情况下,长时间运行的任务或耗时操作应该在单独的线程中执行,以保持主线程的响应性。

  1. 主线程的任务
    主线程负责处理用户界面交互、事件响应和更新UI等任务。长时间运行的任务应该在单独的线程中执行,以避免阻塞主线程并保持应用程序的响应性。

  2. 线程对象的生命周期
    在 mainwindow.cpp 中创建的线程对象 默认是属于主线程 的,因为它们是在主线程的上下文中创建的。即使在 mainwindow.cpp 中创建了一个 QThread 对象和其他工作线程对象,这些对象本身仍然属于主线程的管理

  3. 使用信号槽进行跨线程通信
    在 mainwindow.cpp 中创建的线程对象可以通过信号槽机制与其他对象或线程进行通信。这意味着你可以将主线程的信号连接到工作线程的槽,或者反过来,从工作线程发射信号并在主线程中处理。通过正确使用信号槽,可以实现跨线程的通信和数据传输,而不会阻塞主线程的事件循环。

2. 线程

2.1 QThread

Qt中使用QThread类来管理线程。一般来说,你可以通过以下步骤使用QThread:

  1. 创建一个线程类: 继承自QThread,重写run()方法,在run()方法中编写线程执行的代码。
  2. 启动线程: 通过创建线程对象并调用start()方法来启动线程。
  3. 线程的执行控制: 通常在run()方法中编写线程的主要逻辑。可以通过信号槽机制在主线程和子线程之间进行通信。

下面是一个简单的示例,演示如何使用QThread类创建一个线程并启动它:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 自定义的线程类
class WorkerThread : public QThread
{
public:void run() override{qDebug() << "Worker Thread ID: " << QThread::currentThreadId();// 执行一些耗时的任务for (int i = 0; i < 5; ++i) {qDebug() << "Counting " << i;sleep(1); // 模拟耗时操作}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "Main Thread ID: " << QThread::currentThreadId();WorkerThread thread;thread.start(); // 启动线程// 这里可以继续在主线程中执行其他任务return a.exec();
}

2.2 QtConcurrent::run()

在 Qt 中,QtConcurrent::run() 函数是用于在 后台线程 中执行函数或Lambda表达式的便捷方法。它允许在不需要手动管理线程的情况下,并行地执行耗时的操作,从而避免主线程的阻塞和提高程序的响应性。

  • 线程管理: 是一个线程安全的函数,它会在 Qt 的线程池中执行任务,避免了直接操作底层线程的复杂性。Qt 会自动管理线程池的大小和任务的分发,以提高效率和性能。
  • 线程安全性: 由于任务在后台线程中执行,必须确保访问共享资源时的线程安全性,例如使用互斥量 (QMutex) 或原子操作来保护共享数据的访问。
  • UI 更新: 后台线程中不能直接更新用户界面 (UI),如需要在任务完成后更新 UI,可以使用信号和槽机制,或者在任务完成后通过主线程的事件循环执行相关操作。
  1. 基本语法
QFuture<void> QtConcurrent::run(Function function);
QFuture<void> QtConcurrent::run(Callable callable);

其中:

  • Function 是一个函数指针,指向要在后台线程中执行的函数。
  • Callable 是一个可调用对象,可以是函数对象或Lambda表达式等。
  1. Lambda表达式
QtConcurrent::run([&]() {// 在后台线程中执行的代码// 可以访问外部变量
});

Lambda表达式内部可以访问外部的变量,使用 [&] 捕捉方式可以捕捉所有外部变量的引用,使得在后台线程中可以安全地访问和修改这些变量。

以下是一个简单的示例,演示了如何使用 QtConcurrent::run() 执行一个耗时任务:

#include <QtConcurrent/QtConcurrent>// 定义一个耗时任务
void performTask(int value) {// 模拟耗时操作for (int i = 0; i < value; ++i) {QThread::msleep(100); // 模拟耗时操作,每次休眠100毫秒qDebug() << "Task progress:" << i;}
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int parameter = 5; // 任务的参数// 使用 QtConcurrent::run 启动一个后台任务QFuture<void> future = QtConcurrent::run([&]() {performTask(parameter);});// 等待任务完成future.waitForFinished();qDebug() << "Task completed!";return a.exec();
}

在这个示例中,performTask 函数模拟了一个耗时的任务,使用 QtConcurrent::run() 启动一个后台线程执行这个任务,并通过 QFuture 跟踪任务的执行状态和结果。

2.3 thread 的调用方式

参数说明
detach启动的线程自主在后台运行,当前的代码继续主下执行,不等待新线程结束。
join等待启动的线程完成,才会继续往下执行。

3. 信号槽

信号槽是Qt中一种用于对象间通信的机制,它不仅可以在同一线程中使用,还可以跨线程使用。在跨线程的情况下,信号槽机制能够确保线程安全地进行通信。

  1. 定义信号和槽: 信号是类似于函数的成员,可以被其他对象连接到。槽是接收信号的函数,它们的声明方式与普通的C++成员函数相似,但使用signals和slots关键字来定义。

  2. 连接信号和槽: 使用connect()函数将信号与槽连接起来。Qt中支持跨线程的信号槽连接,当一个信号发射时,与之连接的槽可以在目标线程中被执行。

3.1 connect

在Qt中,使用connect()函数将信号与槽连接起来是实现对象间通信的核心机制之一。通过信号与槽的连接,可以在一个对象发出信号时,触发另一个对象的槽函数执行。下面是几种常见的连接方式示例:

  1. 普通连接方式
    最基本的连接方式是直接使用connect()函数将信号与槽连接起来。这种方式适用于信号和槽的参数列表完全匹配的情况。
// 连接 sender 对象的 signal 信号到 receiver 对象的槽函数 slot
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

在这里:

  • sender 是发出信号的对象。
  • SIGNAL(signal()) 是宏,用于指定信号的名称。
  • receiver 是接收信号的对象。
  • SLOT(slot()) 是宏,用于指定槽函数的名称。
  1. 使用函数指针连接方式
    如果信号和槽的参数列表完全匹配,并且你希望避免使用宏,可以使用函数指针的方式连接。
// 连接 sender 对象的 signal 信号到 receiver 对象的槽函数 slot
connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);

这种方式使用了C++11引入的新特性,使用函数指针取代了宏,更加类型安全。

  1. 使用Lambda表达式连接方式
    从Qt5开始,还可以使用Lambda表达式连接信号和槽。Lambda表达式可以捕获外部变量,使得连接的代码更加灵活和简洁。

三种常用使用方法

// 使用Lambda表达式连接 sender 对象的 signal 信号
connect(sender, &SenderClass::signal, [=](double* value) {// Lambda表达式内的代码,可以执行任意操作// 这里可以访问外部变量receiver->slot();
});
connect(sender, &SenderClass::signal, [&](double* value) {// Lambda表达式内的代码,可以执行任意操作// 这里可以访问外部变量receiver->slot();
});
connect(sender, &SenderClass::signal, [this](double* value) {// Lambda表达式内的代码,可以执行任意操作// 这里可以访问外部变量receiver->slot();
});

Lambda表达式内部可以编写需要执行的逻辑,可以访问当前上下文中的变量。

捕获方式捕获内容权限
[=]捕捉所有外部变量的副本只能访问但不能修改
[&]捕捉所有外部变量的引用可以修改这个信号参数的值
[this]捕捉当前对象的所有成员变量Lambda表达式内部可以访问当前对象的成员变量,但不能修改它们的值

第四种使用方法:访问和修改当前对象的成员变量

connect(sender, &SenderClass::signal, this, [this](double* value) {// Lambda表达式内的代码,可以执行任意操作// 这里可以访问外部变量receiver->slot();
});
  • 访问成员变量: 适合于连接信号时需要访问当前对象的成员变量的情况,例如在槽函数中需要使用类的状态或配置信息。
  • 修改外部变量: 由于使用了 [this] 捕捉方式,Lambda 表达式内部也能够修改当前对象的成员变量的值。
  1. 使用队列连接方式
    在Qt中,还可以使用Qt::QueuedConnection来连接信号和槽,这种方式将信号放入接收对象的事件队列中,在接收对象的事件循环中处理,即使信号和槽在不同的线程中也能正常工作。
// 使用队列连接方式,将 sender 对象的 signal 信号连接到 receiver 对象的槽函数 slot
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()), Qt::QueuedConnection);

这种连接方式适用于需要在不同线程间进行通信的情况。

  1. 指定连接类型的应用

connect第五个参数

参数说明补充
Qt::AutoConnection如果信号和槽在同一线程,则使用Qt::DirectConnection;如果在不同线程,则使用Qt::QueuedConnection。默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection直接调用槽函数,如果信号和槽在同一线程中,相当于直接调用函数。槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
Qt::QueuedConnection将信号投递到接收者的事件队列中,在接收者的事件循环中处理,适合跨线程通信。槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循不之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection特殊的队列连接方式,阻塞发送方直到槽函数执行完毕。槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnectionQt::UniqueConnection用于确保同一连接不会被重复建立。如果同一组件(sender 和 receiver)已经有一个相同类型的连接存在,则connect()函数会失败并返回false。这种方式常用于确保只有一个唯一的连接存在,避免多次连接导致槽函数被多次调用。这个flag可以通过按位或(1)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接爱时,再进行重复的连接就会失败。也就是避免了重复连接。
断开连接的方法该方法虽然不是必须使用的,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。disconnect(sender,SIGNAL(signal),receiver,SLOT(slot), Qt::DirectConnection);

下面是一个简单的示例,演示了如何使用connect()函数来连接信号与槽,并且注释了不同连接类型的使用场景:

#include <QObject>class Sender : public QObject {Q_OBJECTpublic slots:void sendSignal() {emit someSignal();}signals:void someSignal();
};class Receiver : public QObject {Q_OBJECTpublic slots:void handleSignal() {qDebug() << "Signal received in thread: " << QThread::currentThreadId();}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);Sender sender;Receiver receiver;// 使用 Qt::AutoConnection(默认)QObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()));// 使用 Qt::DirectConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::DirectConnection);// 使用 Qt::QueuedConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::QueuedConnection);// 使用 Qt::BlockingQueuedConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::BlockingQueuedConnection);// 使用 Qt::UniqueConnectionbool connected = QObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::UniqueConnection);if (!connected) {qDebug() << "Failed to establish unique connection!";}// 发送信号sender.sendSignal();return app.exec();
}#include "main.moc"

3.2 元对象系统中注册自定义数据类型

在Qt中,信号和槽(Signals and Slots)是一种强大的机制,用于在对象之间进行通信。Qt 会对于标准的数据类型(如 int、QString 等)进行内置支持,但对于自定义的数据类型(如枚举、结构体、类等),Qt 需要能够动态地识别和处理这些类型。因此,需要使用 qRegisterMetaType 来告知 Qt 系统如何处理这些自定义类型:

  • 注册类型: 通过 qRegisterMetaType,Qt 能够在运行时了解如何创建、复制和销毁这些类型的实例。
  • 信号和槽的参数传递: 注册后,可以在信号和槽的连接中使用这些自定义类型作为参数,Qt 能够正确地处理参数的传递和槽函数的调用。

示例代码

namespace Test{enum TestEnum {TestA,TestB,TestC};
}
qRegisterMetaType<Test::TestEnum>("Test::TestEnum");

附录一 信号槽机制与主线程进行通信示例

下面是一个简单的示例,展示了如何在 mainwindow.cpp 中创建一个工作线程,并通过信号槽机制与主线程进行通信。

// mainwindow.cpp#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>// 定义一个工作线程类
class WorkerThread : public QThread
{
public:void run() override{qDebug() << "Worker Thread ID: " << QThread::currentThreadId();// 模拟耗时操作for (int i = 0; i < 5; ++i) {qDebug() << "Counting " << i;sleep(1);}// 发射信号表示工作完成emit workFinished();}signals:void workFinished();
};MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);qDebug() << "Main Thread ID: " << QThread::currentThreadId();// 创建工作线程实例WorkerThread *workerThread = new WorkerThread();// 连接工作线程的工作完成信号到主线程的槽connect(workerThread, &WorkerThread::workFinished, this, &MainWindow::onWorkFinished);// 启动工作线程workerThread->start();
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::onWorkFinished()
{qDebug() << "Work finished signal received in Main Thread ID: " << QThread::currentThreadId();// 这里可以处理工作线程完成后的逻辑
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/43918.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL联合索引最左匹配原则

MySQL中的联合索引(也叫组合索引)遵循最左匹配原则&#xff0c;即在创建联合索引时&#xff0c;查询条件必须从索引的最左边开始&#xff0c;否则索引不会被使用。在联合索引的情况下&#xff0c;数据是按照索引第一列排序&#xff0c;第一列数据相同时才会按照第二列排序。 例…

CVE-2024-27292:Docassemble任意文件读取漏洞复现 [附POC]

文章目录 CVE-2024-27292&#xff1a;Docassemble任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 CVE-2024-27292&#xff1a;Docassemble任意文件读取漏洞复现 [附POC] 0x01 前言 …

冒泡排序与其C语言通用连续类型排序代码

冒泡排序与其C语言通用连续类型排序代码 冒泡排序冒泡排序为交换排序的一种&#xff1a;动图展示&#xff1a;冒泡排序的特性总结&#xff1a;冒泡排序排整型数据参考代码&#xff08;VS2022C语言环境&#xff09;&#xff1a; 冒泡排序C语言通用连续类型排序代码对比较的方式更…

法律行业守护神:知识库+AI大模型,解锁企业知识全周期管理

在法律行业中&#xff0c;搭建一个有效的知识库并进行企业知识全生命周期管理确实是一项不小的挑战。法律环境的复杂性和不断变化的法规要求企业必须持续更新和维护其知识库&#xff0c;以确保所有信息的准确性和实时性。 这种系统化的信息管理不仅有助于提高律师和法律顾问的…

打卡第9天-----字符串

我在自学的时候,看了卡尔的算法公开课了,有些题目我就照葫芦画瓢写了一遍js代码,差不多都写出来了,有暴力解法,有卡尔推荐的思路和方法。话不多说,直接上题上代码吧: 一、翻转字符串里的单词 leetcode题目链接:151. 反转字符串中的单词 题目描述: 给你一个字符串 s…

5个自动化面试题,助你过关斩将!

面试时&#xff0c;自动化是软件测试高频面试内容&#xff0c;通过学习和准备面试题&#xff0c;你会对可能遇到的问题有所准备&#xff0c;从而减轻面试时的紧张感&#xff0c;让你在面试中稳操胜券&#xff01; 今天&#xff0c;分享一些在面试中可能会遇到的自动化测试面试…

软件架构之测评方法

软件架构之测评方法 第 11 章&#xff1a;测试评审方法11.1 测试方法11.1.1 软件测试阶段11.1.2 白盒测试和黑盒测试11.1.3 缺陷的分类和级别11.1.4 调试 11.2 评审方法11.3 验证与确认11.4 测试自动化11.5 面向对象的测试 第 11 章&#xff1a;测试评审方法 软件测试与评审是…

大学生暑假“三下乡”社会实践工作新闻投稿指南请查收!

近年来&#xff0c;大学生暑期“三下乡”社会实践工作方兴未艾&#xff0c;越来越多的大学生通过参与“三下乡”实践工作&#xff0c;走出校园&#xff0c;深入基层&#xff0c;体验农村生活&#xff0c;服务农民&#xff0c;促进农村经济社会发展&#xff0c;实现了理论与实践…

算能科技,致力于成为全球领先的通用算力供应商

算能致力于成为全球领先的定制算力提供商&#xff0c;专注于RISC-V、TPU处理器等算力产品的研发和推广应用。公司遵循全面开源开放的生态理念&#xff0c;携手行业伙伴推动RISC-V高性能通用计算产业落地&#xff1b;打造覆盖“云、边、端”的全场景产品矩阵&#xff0c;为数据中…

【eNSP模拟实验】三层交换机实现VLAN通信

实验需求 让PC1和PC2能够互相通讯&#xff0c;其中PC1在vlan10中&#xff0c;PC2在vlan20中。 实验操作 首先把PC1和PC2都配置好ip&#xff0c;配置好之后&#xff0c;点击右下角的应用 然后&#xff0c;在S2交换机&#xff08;S3700&#xff09;上做如下配置 #进入系统 <…

mvvm模式

MVVM&#xff08;Model-View-ViewModel&#xff09;模式是一种软件设计模式&#xff0c;特别适用于构建用户界面&#xff08;UI&#xff09;应用程序&#xff0c;尤其是使用WPF&#xff08;Windows Presentation Foundation&#xff09;、Silverlight和其他XAML技术的应用程序。…

【Redis】Redis十大类型

文章目录 前言一、string字符串类型二、List列表类型三、 Hash表四、 Set集合五、 ZSet有序集合六、 GEO地理空间七、 HyperLogLog基数统计八、Bitmap位图九、bitfield位域十、 Stream流10.1 队列指令10.2 消费组指令10.3 ACK机制 前言 redis是k-v键值对进行存储&#xff0c;k…

Mac上pyenv的安装及使用

Mac上pyenv的安装及使用 安装 brew update brew install pyenv 报错 git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallowgit -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow那就执行这2句 还报错 git -C /…

【最经典的79个】软件测试面试题(内含答案)提前备战“金九银十”

001.软件的生命周期(prdctrm) 计划阶段(planning)-〉需求分析(requirement)-〉设计阶段(design)-〉编码(coding)->测试(testing)->运行与维护(running maintrnacne) 测试用例 用例编号 测试项目 测试标题 重要级别 预置条件 输入数据 执行步骤 预期结果 0002.问&…

“论软件维护方法及其应用”写作框架,软考高级论文,系统架构设计师论文

论文真题 软件维护是指在软件交付使用后&#xff0c;直至软件被淘汰的整个时间范围内&#xff0c;为了改正错误或满足 新的需求而修改软件的活动。在软件系统运行过程中&#xff0c;软件需要维护的原因是多种多样的&#xff0c; 根据维护的原因不同&#xff0c;可以将软件维护…

CVE-2024-34351 漏洞复现

CVE-2024-34351&#xff0c;由Next.js异步函数createRedirectRenderResult导致的SSRF。 影响版本&#xff1a;13.4.0< Next.js < 14.1.1 参考文章&#xff1a; Next.js Server-Side Request Forgery in Server Actions CVE-2024-34351 GitHub Advisory Database Gi…

数据库Doris的手动分桶和自动分桶

在Doris中,分桶(Bucketing)是为了更好地管理和查询数据,将数据分成多个小的逻辑单元。分桶可以通过手动或自动的方式进行配置,每种方式各有其特点和适用场景。 Doris 支持两层的数据划分。第一层是分区(Partition),支持 Range 和 List 的划分方式。第二层是Bucket(Tab…

RK3568平台开发系列讲解(内存篇)Linux进程内存的消耗统计

🚀返回专栏总目录 文章目录 一、VSS(Virtual Set Size)二、RSS(Resident Set Size)三、PSS(Proportional Set Size)四、USS(Unique Set Size)五、其他工具Linux 提供了多种进程内存占用的度量指标, 它们反映了不同的内存使用特征: VSS 反映进程虚拟内存总需求, 包括未…

2.python条件语句与循环

1.概述 通过条件语句来判断&#xff0c;条件成立执行某些代码&#xff0c;条件不成立则不执行这些代码 2.if语句 if条件&#xff1a;条件成立执行的代码...... 下方代码没有缩进到if语句块&#xff0c;所以和if条件无关if…else if条件&#xff1a;条件成立执行的代码.....…

Nature Communications|柔性无感智能隐形眼镜(柔性传感/可穿戴电子/柔性电子)

南京大学徐飞(Fei Xu)、陆延青(Yanqing Lu)、陈烨(Ye Chen)和江苏省人民医院袁松涛(Songtao Yuan)团队,在《Nature Communications》上发布了一篇题为“Frequency-encoded eye tracking smart contact lens for human–machine interaction”的论文。论文内容如下: 一、 摘…