Qt 无法弄清楚如何在我的程序中线程化我的返回值

Posted

技术标签:

【中文标题】Qt 无法弄清楚如何在我的程序中线程化我的返回值【英文标题】:Qt can't figure out how to thread my return value in my program 【发布时间】:2014-10-29 13:19:40 【问题描述】:

我已经阅读了网络上关于如何在 Qt such as the article here 中使用多线程应用程序的各种文章,并且我注意到 Qt 也更新了他们的 official documentation 关于这个主题,但是我仍然在努力理解如何创建一个线程,做一些图像处理并返回一个新的 QImage 来更新我的 GUI。

我正在努力澄清的事情是:

    我在哪里放置连接代码,在大多数示例中,我看到在对象的构造函数中声明的连接。

    为什么一个连接语句需要这么多行来完成一个进程? IE。在我的情况下,我有一个滑块来更改 QGraphicsView 上图像的饱和度,我想生成一个线程来处理图像像素的操作,然后将格式化的 QPixmap 返回到我的 GUI 并运行渲染方法来将新图像绘制到画布上(我不认为我可以从我的线程更新我的画布?)

在第 2 点之后,这是我为我的线程编写的当前代码(我没有继承 QThread,我认为我正确地遵循了文档。)

WorkerThread.h

#include "sliders.h"

class WorkerThread : public QObject

    Q_OBJECT
public:
    WorkerThread();
    ~WorkerThread();

public slots:
    void modifySaturation(const int, const QPixmap);

signals:
    void SaturationChanged(const QPixmap);

private:
    Sliders *slider;

;

WorkerThread.cpp

WorkerThread::WorkerThread()




WorkerThread::~WorkerThread()




// Create a new Sliders object on the thread (declaring in construct would place it on the main thread?)
// Call the modifySaturation() method in the slider class and store its returned QPixmap into the variable to emit it back to the GUI
void WorkerThread::modifySaturation(const int value, const QPixmap image)

   slider = new Sliders;
   QPixmap img = slider->modifySaturation(value, image);
   emit resultReady(img);

希望上面的 cmets 传达了我希望将新创建的 Pixmap 发送回主线程以绘制到 GUI 方面的想法。

我遇到麻烦的步骤是编写逻辑来桥接我的主线程和工作线程之间的连接,到目前为止,我已经在我的mainwindow.h 中创建了一个名为“thread”的QThread 对象,然后在我的@ 987654329@我做以下:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)

    // Instanciate a scene object that we can draw to and then set up the ui
    scene  = new QGraphicsScene(this);
    filter = new Filters;
    worker = new WorkerThread;
    ui->setupUi(this);

    thread = new QThread;
    worker->moveToThread(&thread);

    // This is what I am struggling to understand
    connect(thread, SIGNAL(started()), worker, SLOT(modifySaturation(int,QPixmap)));
    connect(worker, SIGNAL(SaturationChanged(QPixmap)), MainWindow, SLOT(onSaturationChanged()));
    

// Public slot on my main window to update the GUI
void MainWindow::onSaturationChanged(QPixmap)

    // image is a private member describing the current loaded image
    m_image = QPixmap;
    renderImageToCanvas();

根据我的阅读,我应该在开始任务时生成一个新线程,但我该怎么做:

    将此线程用于多种方法(更改饱和度、更改亮度、更改色调...),我是否需要为每个不同的任务创建一个新线程(这似乎有点过于复杂)? 如何连接饱和度滑块的value changed 方法以在新线程上启动计算,然后使用主窗口中的OnSaturationChanged 插槽将其返回以更新GUI?

【问题讨论】:

请不要在主线程以外的地方使用QPixmap。请改用QImage。否则,当你运行你的应用程序时,你会收到一堆警告,谁知道还会发生什么。 @thuga 我将更改我的代码,感谢您突出显示该格式 【参考方案1】:

正如您提到的精彩文章 How To Really Truly use QThread,让我们开始通过分解来解释您不确定的代码

QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

这段代码通常会放在主线程的一个对象中,也许在MainWindow中:-

创建一个新的线程对象 - QThread 实际上更像是一个线程控制器而不是一个线程

QThread* thread = new QThread;

创建一个新的工作对象。这是一个可以在不同线程上工作的对象。由于这可以移动到不同的线程,请注意您可以创建多个对象并将它们移动到同一个线程

Worker* worker = new Worker();

将对象及其子对象移动到新线程

worker->moveToThread(thread);

设置有用的连接来监视和控制工作人员。让我们从任何错误开始,这样我们就知道工人是否有问题

connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));

为了启动worker对象处理,我们将线程的started()信号连接到worker的process()槽。在您的示例中,进程将是 modifySaturation 槽

connect(thread, SIGNAL(started()), worker, SLOT(process()));

worker处理完后,如果是唯一的对象,需要退出清理,所以线程应该退出

connect(worker, SIGNAL(finished()), thread, SLOT(quit()));

为了整理,现在worker和thread都不需要了,一定要自己整理

connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

最后,让我们开始调用thread->start(),它会触发我们之前连接到worker的process()函数的初始started()信号

thread->start();

考虑到所有这些,让我们解决提出的问题:-

1 我怎样才能将这个线程重用于多种方法(改变饱和度、改变亮度、改变色调......),我是否需要为每个不同的任务创建一个新线程(这似乎有点过于复杂) ?

不,您不需要为每个方法创建一个新线程。您可以使用当前对象并扩展它来完成所有处理,通过来自主线程的信号和槽来控制它,或者您可以为每个方法创建一个单独的对象并将它们全部移动到新线程。

如果您使用移动到新线程的多个对象,请确保在其他对象仍在使用该线程时,不要连接对象的finished() 信号来调用线程上的quit()。但是,当您完成对象和线程时,您仍然需要清理它们。

2 为什么一个连接语句需要这么多行来完成一个进程?即在我的情况下,我有一个滑块来更改 QGraphicsView 上图像的饱和度,我想生成一个线程来处理图像像素的操作,然后将格式化的 QPixmap 返回到我的 GUI 并运行渲染方法来绘制新的图像到画布(我不认为我可以从我的线程更新我的画布?)

一般规则是您只能从主线程更新图形对象(小部件、图形项等)。 (有一个例外,但它超出了本次讨论的范围,在这里不相关。)

当只使用一个对象时,在多个连接信号中,三个用于在完成时删除对象,一个用于处理错误消息,最后一个连接确保工作对象在线程开始时启动。

没有什么可以阻止您通过创建线程并首先启动它、创建工作对象、连接相关信号并随后将它们移动到线程来改变这一点,但是您需要触发工作人员开始做某事,例如处理饱和度,一旦它们被移动到新线程。

QThread* pThread = new QThread;
pThread->start();

Worker* worker1 = new Worker();
Worker* worker2 = new Worker();
Worker* worker3 = new Worker();

worker1->moveToThread(pThread);
worker2->moveToThread(pThread);
worker3->moveToThread(pThread);

这里的工作对象已经被移到了新线程,该线程正在运行。但是,工作对象是空闲的。如果没有连接,我们可以调用要调用的插槽。假设进程槽采用整数参数...

QMetaObject::invokeMethod( worker1, "process", Q_ARG( int, param ) );
QMetaObject::invokeMethod( worker2, "process", Q_ARG( int, param ) );
QMetaObject::invokeMethod( worker3, "process", Q_ARG( int, param ) );

因此,正如您在此处看到的,您并不总是需要连接信号,但它很方便。

【讨论】:

【参考方案2】:

我会回答你的一个问题,因为 Merlin 很好地涵盖了其余的问题。

how can I connect the value changed method of my saturation slider to launch the computation on a new thread and then return it to update the GUI using the OnSaturationChanged slot in my main window?

例如,在您的MainWindow::OnSaturationChanged 插槽中,您可以发出一个信号,将QImageslider 值传递给您的线程。此信号将连接到您的 WorkerThread 的插槽,该插槽会进行一些图像处理。

mainwindow.h

public slots:        
    void addNewImage(QImage image);

signals:
    void requestImageUpdate(QImage image, int sliderValue);

mainwindow.cpp

    //in your MainWindow constructor or wherever you create your worker...
    connect(this, SIGNAL(requestImageUpdate(QImage, int)), worker, SLOT(updateImage(QImage, int)));
    connect(worker, SIGNAL(imageUpdated(QImage)), this, SLOT(addNewImage(QImage)));
    ...

void MainWindow::OnSaturationChanged()

    emit requestImageUpdate(myImage, slider->value());


void MainWindow::addNewImage(QImage image)

    //update the image in your graphics view or do whatever you want to do with it

workerthread.h

public slots:
    void updateImage(QImage image, int sliderValue);

signals:
    void imageUpdated(QImage newImage);

workerthread.cpp

void WorkerThread::updateImage(QImage image, int sliderValue)

    QImage newImage; // you might no need this, this is just an example
    ....
    emit imageUpdated(newImage);

附:仅在主线程中使用QPixmap。在其他线程中使用QImage

【讨论】:

我已经修改了我的代码库并使用了你的答案作为参考,我非常感谢你,这对我来说终于有意义了(来自 Java 背景的奇怪的学习曲线)......虽然我可以'不要接受这个作为答案,因为 Merlin 解释了我无法理解的理论。编码愉快! 我想知道你能否回答我最后一个问题,为什么在主线程之外使用QPixmap 很危险?: @Alex QPixmap 做了一些与 GUI 相关的事情。 Note that the pixel data in a pixmap is internal and is managed by the underlying window systemQImage is designed and optimized for I/O, and for direct pixel access and manipulation, while QPixmap is designed and optimized for showing images on screen.一般规则是,如果该类在 Qt 文档中未标记为线程安全或可重入,则在主线程之外使用它很可能是不安全的。

以上是关于Qt 无法弄清楚如何在我的程序中线程化我的返回值的主要内容,如果未能解决你的问题,请参考以下文章

在我的 Qt C++ 应用程序中使用 Anaconda 的 Python

无法弄清楚为啥我在我的 jsonp 序列化中得到重复数据

我需要弄清楚如何将我的类库中的枚举绑定到我的应用程序中的单选按钮

Crypto++ 无法构建 Qt 应用程序

空指针异常。无法弄清楚如何修复

我无法弄清楚我的代码有啥问题