在Qt中,信号槽机制是允许对象之间通信的核心特性。
在处理线程时,非常必要了解信号和插槽在多线程环境中的工作方式。
关于connect函数的“第五个参数”,指的是connect()
方法中的Qt::ConnectionType
参数。此参数确定连接的类型,这在多线程应用程序中非常重要。
现在让我们通过一个例子深入研究Qt的信号槽机制中的连接连接的概念,我将引导你理解它的工作原理。
假设在一个Qt应用程序中有两个线程:线程A和线程B
。线程A是主GUI线程,而线程B是后台工作线程
。你希望线程B在完成任务时通知线程A
,因此你将使用Qt的信号槽机制
来实现这一点。
在典型
的直接连接(单线程应用程序的默认设置)中,当发出信号时,插槽立即执行
。但是,在多线程
方案中,这种立即执行
可能不安全或不可行,因为可能需要插槽
来操作GUI组件
或访问仅可从主线程安全访问
的资源。
这就是连接的作用。
使用QueuedConnection
,当线程B
发出信号时,该信号被放置在线程A
的事件队列
中,所以不会立即执行。然后,当线程A
的事件循环下一次被处理
时,它在队列中检测到这个信号,并执行相应的slot函数
。这样,即使信号是从不同的线程发出的,slot函数也会在接收对象所在的线程中安全地执行,从而防止任何线程安全问题。
为了帮助巩固这一概念,你能想到一个简单的场景,在这种类型的连接是必要的吗?
例如,假设线程B正在执行文件下载,您希望在完成此下载后更新GUI(线程A)中的进度条。在这种情况下,您将如何设置信号和插槽,使用一个连接?
现在请关闭博客,给你5分钟来思考…
two thousand years later…
下面具体展开来讲讲:
我可以使用QueuedConnection将线程A
和B
进行连接,线程B
边下载边计算 已下载/总文件大小
,分别在10%,20%,30%...100%
向线程A发送一个信号,该信号的参数
就是下载的百分比
,然后线程A
收到后,根据线程B的参数来更新GUI
Q t Qt Qt的 c o n n e c t connect connect 函数用于将信号与槽连接起来,但它本身既不是同步的也不是异步的。这个函数的作用是建立一个连接,当信号发生时,相应的槽函数会被调用。
关于同步或异步的行为,这取决于信号和槽函数的执行方式,而不是 connect 函数本身。
在 Qt 中,
- 如果信号和槽都在同一个线程中,槽函数的调用通常是同步的,即在信号发射后立即执行。
- 如果信号和槽位于不同的线程,Qt 使用消息队列来传递信号,这时槽函数的调用是异步的,会在目标线程的事件循环中稍后执行。
因此,connect 函数的作用是建立信号和槽之间的连接,而信号到槽的调用机制决定了同步或异步行为。
实现边下载某个网页的文件,边实时更新当前的进度条
#include
#include
#include
#include
#include
#include
class Downloader : public QObject
{
Q_OBJECT
public:
Downloader(const QUrl& url, QProgressBar* progressBar, QObject* parent = nullptr)
: QObject(parent), url(url), progressBar(progressBar)
{
//这一行代码仅仅是创建了一个 QNetworkAccessManager 的实例,并将其分配给 manager 指针。这一行代码本身并没有建立连接或发起网络请求。
manager = new QNetworkAccessManager(this);
//当网络请求完成或下载进度发生变化时,相关的槽函数将被调用。
//即,下载完成后,会执行onFinished函数进行收尾工作
connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onFinished);
}
void startDownload()
{
reply = manager->get(QNetworkRequest(url));//这句话是异步的,并不会阻塞
//当下载进度发生变化时,相关的槽函数将被调用。
connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgress);
}
private slots:
void onFinished(QNetworkReply* reply)
{
if (reply->error() == QNetworkReply::NoError)//下载很顺利,没有错误
{
// 下载完成
QByteArray data = reply->readAll();
// 处理下载的数据
//to do ...
//eg:将下载文件解压、对比、等等
// 清理资源
reply->deleteLater();
manager->deleteLater();
}
else
{
// 处理下载错误
//搞个QMessageBox提示框报错
}
}
//当下载进度发生变化时,相关的槽函数将被调用。
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
// 更新进度条
int progress = static_cast<int>(bytesReceived * 100 / bytesTotal);
progressBar->setValue(progress);
}
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
QUrl url;
QProgressBar* progressBar;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建主窗口
QWidget mainWindow;
mainWindow.setWindowTitle("下载并更新进度条示例");
// 创建进度条
QProgressBar progressBar(&mainWindow);
progressBar.setGeometry(10, 10, 280, 30);
// 下载文件的URL
QUrl downloadUrl("https://example.com/examplefile.txt");
// 创建Downloader实例并开始下载
Downloader downloader(downloadUrl, &progressBar);//实例
downloader.startDownload();//下载
// 显示主窗口
mainWindow.show();
return a.exec();
}
#include "main.moc"
因为:reply = manager->get(QNetworkRequest(url));
是异步的,,因为是单线程,所以connect
函数是同步的(即在信号发射后立即执行)
.
使用了 Qt 的 QNetworkAccessManager 和 QNetworkReply 来处理网络下载,并且更新了 GUI 的 QProgressBar 来显示下载进度。这个设计考虑到了 Qt 的事件驱动和异步处理机制,因此在理论上,GUI 不应该在下载过程中出现卡顿
。
异步网络请求:使用 QNetworkAccessManager 的 get 方法来发起网络请求是异步的
。这意味着 get 方法会立即返回,而不会等待下载完成
。下载在后台进行,不会阻塞主线程
。
信号和槽机制:当下载进度更新或下载完成时,将触发信号,如 downloadProgress 和 finished。这些信号连接到槽函数 onDownloadProgress 和 onFinished
,它们会在主线程
中被调用,但由于它们的执行时间通常很短,不太可能导致 GUI 卡顿
。
GUI更新:进度条的更新发生在 onDownloadProgress 槽中,这个操作通常很快,不会对 GUI 性能产生显著影响
。
综上所述,如果下载任务和数据处理不是特别重或复杂,这个设计通常不会导致 GUI 卡顿。但是,如果在 onFinished 槽中的数据处理非常繁重(例如,处理非常大的文件或进行复杂的数据操作),那么可能会影响 GUI 的响应性。在这种情况下,可以考虑将数据处理任务移到另一个线程中去,以保持 GUI 的流畅性。
GUI:主线程A
下载的任务:线程B
线程B在下载的同时,会给计算下载的百分比,并将百分比发送给线程A
#include
#include
#include
#include <...>
class Downloader : public QObject {
Q_OBJECT
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
QUrl url;
public slots:
void download(QString Url) {// 六
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onFinished);
reply = manager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgress);
/*
for (int downloaded = 10; downloaded <= totalSize; downloaded += 10) {
QThread::msleep(100); // 模拟下载过程, 使用msleep代替sleep以增加进度更新的频率
int percent = static_cast(100.0 * downloaded / totalSize);
emit progress(percent);
}
emit finished();
*/
}
void onFinished(QNetworkReply* reply)
{
emit finished(reply);
}
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
int progress = static_cast<int>(bytesReceived * 100 / bytesTotal);
emit progress(progress);
}
signals:
void progress(int percent);
void finished();
};
/
class MyWidget : public QObject {
Q_OBJECT
public:
MyWidget() {// 二
// 将下载器移至工作线程
downloader.moveToThread(&workerThread);
// 连接开始下载的信号到下载器的下载槽// 五
connect(this, &MyWidget::startDownload, &downloader, &Downloader::download, Qt::QueuedConnection);
// 连接下载器的进度信号到处理进度的槽
connect(&downloader, &Downloader::progress, this, &MyWidget::handleProgress, Qt::QueuedConnection);
// 连接下载完成信号
connect(&downloader, &Downloader::finished, this, &MyWidget::handleFinished, Qt::QueuedConnection);
// 启动工作线程
workerThread.start();
}
~MyWidget() {
workerThread.quit();
workerThread.wait();
}
void startDownloading(QString Url) {// 四
emit startDownload(Url); // 发送信号来开始下载
}
public slots:
void handleProgress(int percent) {
qDebug() << "Download progress:" << percent << "%";
// 这里可以更新 GUI 进度条
}
void handleFinished(QNetworkReply* reply) {
if (reply->error() == QNetworkReply::NoError)//下载很顺利,没有错误
{
qDebug() << "Download finished!";
// 处理下载完成事件,例如关闭进度条、弹出通知等操作
// 下载完成
QByteArray data = reply->readAll();
// 处理下载的数据
//to do ...
//eg:将下载文件解压、对比、等等
// 清理资源
reply->deleteLater();
manager->deleteLater();
}
else
{
// 处理下载错误
//搞个QMessageBox提示框报错
}
}
signals:
void startDownload(int totalSize);
private:
QThread workerThread;
Downloader downloader;
};
#include "main.moc"
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
MyWidget widget;// 一
QString Url = "https://example.com/examplefile.txt";
widget.startDownloading(Url); // 三
return app.exec();
}
// 连接开始下载的信号到下载器的下载槽
connect(this, &MyWidget::startDownload, &downloader, &Downloader::download, Qt::QueuedConnection);
// 连接下载器的进度信号到处理进度的槽
connect(&downloader, &Downloader::progress, this, &MyWidget::handleProgress, Qt::QueuedConnection);
// 连接下载完成信号
connect(&downloader, &Downloader::finished, this, &MyWidget::handleFinished, Qt::QueuedConnection);