The fifth parameter of the qt slot function(qt Connect函数的第五个参数)

The fifth parameter of the qt slot function

  • preface(前言)
  • qt Connect函数的第五个参数(这里专门指:QueuedConnection)-多线程编程的一些原理
    • 1)初步理解
    • 2)加深理解
  • demo
    • 在写demo之前,我有一个疑问?qt的connect函数是同步的还是异步的?
    • 单线程
      • 解析
    • 多线程
      • 解析
  • 附件

preface(前言)

在Qt中,信号槽机制是允许对象之间通信的核心特性。
在处理线程时,非常必要了解信号和插槽在多线程环境中的工作方式。
关于connect函数的“第五个参数”,指的是connect()方法中的Qt::ConnectionType参数。此参数确定连接的类型,这在多线程应用程序中非常重要。

qt Connect函数的第五个参数(这里专门指:QueuedConnection)-多线程编程的一些原理

1)初步理解

现在让我们通过一个例子深入研究Qt的信号槽机制中的连接连接的概念,我将引导你理解它的工作原理。


假设在一个Qt应用程序中有两个线程:线程A和线程B线程A是主GUI线程,而线程B是后台工作线程。你希望线程B在完成任务时通知线程A,因此你将使用Qt的信号槽机制来实现这一点。
典型的直接连接(单线程应用程序的默认设置)中,当发出信号时,插槽立即执行但是,在多线程方案中,这种立即执行可能不安全或不可行,因为可能需要插槽来操作GUI组件或访问仅可从主线程安全访问的资源。
这就是连接的作用。
使用QueuedConnection,当线程B发出信号时,该信号被放置在线程A事件队列中,所以不会立即执行。然后,当线程A事件循环下一次被处理时,它在队列中检测到这个信号,并执行相应的slot函数。这样,即使信号是从不同的线程发出的,slot函数也会在接收对象所在的线程中安全地执行,从而防止任何线程安全问题

2)加深理解

为了帮助巩固这一概念,你能想到一个简单的场景,在这种类型的连接是必要的吗?

例如,假设线程B正在执行文件下载,您希望在完成此下载后更新GUI(线程A)中的进度条。在这种情况下,您将如何设置信号和插槽,使用一个连接?

现在请关闭博客,给你5分钟来思考…

two thousand years later…

下面具体展开来讲讲:
我可以使用QueuedConnection将线程AB进行连接,线程B边下载边计算 已下载/总文件大小,分别在10%,20%,30%...100%向线程A发送一个信号,该信号的参数就是下载的百分比,然后线程A收到后,根据线程B的参数来更新GUI

demo

在写demo之前,我有一个疑问?qt的connect函数是同步的还是异步的?

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 不应该在下载过程中出现卡顿

  • 下面是关于代码中的几个关键点的解释:
  1. 异步网络请求:使用 QNetworkAccessManager 的 get 方法来发起网络请求是异步的。这意味着 get 方法会立即返回,而不会等待下载完成。下载在后台进行,不会阻塞主线程

  2. 信号和槽机制:当下载进度更新或下载完成时,将触发信号,如 downloadProgress 和 finished。这些信号连接到槽函数 onDownloadProgress 和 onFinished,它们会在主线程中被调用,但由于它们的执行时间通常很短,不太可能导致 GUI 卡顿

  3. 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);

附件

  1. 分别实现单线程与多线程下,qt下载文件并实时更新进度条的流程图

你可能感兴趣的:(Qt,qt,多线程)