【问题标题】:Correct way of threading in QtQt中正确的线程方式
【发布时间】:2026-02-10 12:15:01
【问题描述】:

我的图片加载很耗时(图片很大),加载时也做了一些操作。我不想阻止应用程序 GUI。

我的想法是在另一个线程中加载图像,发出图像已加载的信号,然后用该图像重绘视图。

我的做法:

void Window::loadImage()
{ 
    ImageLoader* loaderThread = new ImageLoader();
    connect(loaderThread,SIGNAL(imageLoaded()),this,SLOT(imageLoadingFinished());
    loaderThread->loadImage(m_image, m_imagesContainer, m_path);
}
void Window::imageLoadingFinished()
{
    m_imagesContainer->addImage(m_image);
    redrawView();
}

class ImageLoader : public QThread
{
    Q_OBJECT
    public:
       ImageLoader(QObject *parent = 0) : m_image(NULL), m_container(NULL)

       void loadImage(Image* img, Container* cont, std::string path)
       {
            m_image = img;
            m_container = cont;
            ...
            start();
       }
    signals:
       void imageLoaded();
    protected:
       void run()
       {
           //loading image and operations on it
           emit imageLoaded();
       }
    protected:
       Image* m_image;
       Container* m_container;
}

我基于 Qt 中的 quedcustomtype 示例编写此代码。在 * 中搜索和搜索时,我还发现子类化 QThread 不是一个好主意。

所以问题是正确的方法是什么?正如我所说,我希望在另一个线程中完成非阻塞 GUI、加载和操作,并发出表示加载完成的信号。发出信号后,应重新绘制视图。 我对多线程了解不多,但想了解或有足够的知识来了解基本思想。

【问题讨论】:

  • 如果 QThread 本质上是坏的,它将被从库中取出。而不是仅仅相信“永远不要使用 QThread”,您应该找出人们认为存在哪些问题,确定它们是否对您有效,并决定如何使用它们。或者,您可以使用特定于平台的(不一定是可移植的)线程机制,例如 pthreads。只是不要在你的线程中驱动 UI,而是使用 Qt 事件来告诉 UI 线程来驱动它。
  • @mah 我不认为 QThread 天生就不好,但是有一些different understandings how to use it
  • @Andreas 那篇博文似乎表明很多人不知道如何正确使用 QThread。帖子底部的链接有一个示例页面,其重点似乎是:要正确继承 QThread,您必须重载 run() 方法。但是,当 Qt 的文档看起来很清楚时,我真的不确定为什么会像您的链接所暗示的那样对滥用模式产生一种看法(有效与否)。以doc.qt.digia.com/qt/threads-starting.html 为例。
  • *.com/questions/4093159/… 可能感兴趣

标签: c++ multithreading qt qthread


【解决方案1】:

使用QtConcurent 框架。

#include <QtConcurentRun>
#include <QFutureWatcher>

//....
class Window: public QWidget /*or something*/
{
//....
private:
    QFutureWatcher<QImage> _wacther; //this object will signal when loading finished
};

//...

void Window::loadImage()
{
   connect(&_watcher, SIGNAL(finished(), SLOT(finishLoading());
    _wacther.setFuture(QtConcurent::run<QImage>(this, &Window::doLoadImage));
}

QImage Window::doLoadImage() //this function will be executed in the new thread. SHOULD BE Thread Safe
{
   return someImage;
}

void window::finishLoading()
{
    QImage result = _watcher.result();
}

【讨论】:

  • 好主意,你打败了我。挑剔的是,全局范围内的 _watcher 变量不应以 _ 开头,因为它是在全局范围内保留的。见*.com/questions/228783/…
  • @PorkyBrain,应该是Window的成员。让我把它放在那里。
  • 注意拼写错误
  • 我认为我已经简化了很多。我不想复杂化并发布了我所拥有的示例。问题在于调用QtConcurrent::run&lt;MyStructure*&gt;(*this,&amp;ClassName::function)。输出显示QObject::QObject(const QObject&amp;) is private。我试过QtConcurent::run&lt;MyStructure*&gt;(&amp;className::function,但也失败了。我看到复制 somwere 已禁用。不知道我是否在此评论中提供了足够的信息。如果不是,请说明需要澄清的地方。
  • 请考虑 Qt 家伙不认为 QtConcurrent 是最好的方法:mail-archive.com/development@qt-project.org/msg07794.html。我喜欢它,但我一直不鼓励使用它。
【解决方案2】:

我想这是最好的方法:

#include <QApplication>
#include <QLabel>
#include <QThread>

class ImageLoader : public QObject
{
   Q_OBJECT
public:
   ImageLoader() : QObject() {
      moveToThread(&t);
      t.start();
   }
   ~ImageLoader() {
      qDebug("Bye bye!");
      t.quit();
      t.wait();
   }

   void requestImage(QString absPath) {
      QMetaObject::invokeMethod(this, "loadImage", Q_ARG(QString, absPath));
   }

public slots:
   void loadImage(QString absPath) {
      // Simulate large image.
      QImage image(absPath);
      sleep(10);
      qDebug("Image loaded!");
      emit imageReady(image);
   }

signals:
   void imageReady(QImage image);

private:
   QThread t;
};

class MyLabel : public QLabel
{
   Q_OBJECT
public:
   MyLabel() : QLabel() {}

   void mousePressEvent(QMouseEvent* ev) {
      Q_UNUSED(ev);
      qDebug("I got the event!");
   }

public slots:
   void setImage(QImage image) {
      setPixmap(QPixmap::fromImage(image));
      resize(image.width(), image.height());
      qDebug("Image shown!");
   }
};

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);

   MyLabel label;
   label.show();

   ImageLoader imageLoader;
   QObject::connect(&imageLoader, SIGNAL(imageReady(QImage)), &label, SLOT(setImage(QImage)));
   imageLoader.requestImage(some_abs_path);

   return a.exec();
}

#include "main.moc"

我也喜欢 QtConcurrent,但考虑到不鼓励使用它:http://www.mail-archive.com/development@qt-project.org/msg07794.html

【讨论】:

  • 您将 ImageLoader 移动到线程,但与此同时,您还将线程移动到其“自己的”线程中,因为它是一个成员变量。 QThread 只是线程管理器,不应移动。查看官方文档如何使用 Controller(围绕线程的包装器)和 worker(使用 moveToThread())
  • 不,moveToThread 根据文档移动儿童:doc.qt.io/qt-5/qobject.html#moveToThread。那 QThread 不是一个孩子,它只是一个成员。文档清楚地说明了doc.qt.io/qt-5/qobject.html#thread-affinity:“调用 moveToThread() 时,对象的成员变量将保留在旧线程中”。这意味着 QThread 将保留在原始线程中,并且仅移动对象,因为没有子线程。
  • 你能做一个快速编辑,以便我可以改变我的投票吗?看来加个空格就够了