本文介绍的是实例操作 Qt 多线程 后台创建缩略图,不多说,更多资料参考本文末尾,先来看本文内容。
起因是在qtcn上看到有人说创建缩略图的API执行很慢,并且阻塞了gui的正常运转,需要改成多线程来实现,但他不清楚如何实现,希望能有个例子。 这位同学的问题揭示了Qt里最需要用到多线程的一个用例,那就是防止一切阻塞GUI线程的操作。 Qt GUI编程有个最基本但很多人不熟悉的原则是所有的操作都不能是阻塞的, 同时所有的操作都不能占用很多CPU, 这个原则是针对GUI线程来说的, 但后者对子线程也同样适用(这一点我们后边继续八)。
熟悉Qt的人都应该有所了解,Qt的图形处理是基于事件循环的,这一点和大多数的GUI工具库一致。事件循环简单来说就是个死循环,里面读取系统外设的事件,然后见招拆招。 Qt里的Events, Signals,Timer都在事件循环里分发,所以,如果阻塞了事件循环用脚趾头想想也能知道会有多么严重的后果,最明显的就是界面不刷新,也不能和用户交互,所以这就要求我们在写程序的时候不管是事件处理函数还是槽函数里都不能进行占用CPU时间的操作,特别要杜绝阻塞性的操作如串口数据读取、杜绝长时间的高CPU占用率的操作如死循环和大数据量的计算等。 However,“人在河边走, 哪能不湿鞋”呢, 总有需要用到这些操作的时候,Qt也提供了解决方案,那就是多线程。
Qt的多线程类有很多,改天开帖详细介绍,这里只八最核心的QThread类。 该类的用法是Posix多线程的用法的简化版, 只需要派生一个QThread的子类,实现其中的run虚函数就大功告成, 用的时候创建该类的实例,调用它的start方法,理论上非常简单。 笔者个人觉得写多线程的程序首先要去恶补一下多线程的知识, Qt说到底只是一个工具,它的API包装得再好也得用的人懂得如何去使用才能发挥最好的作用。 Qt的文档中关于多线程的编程文章挺多, 另外还列出了一系列的推荐读物, 想把多线程程序写好的人一定要看。
前面啰嗦了这么多无非是想告诉大家,写多线程的程序单靠Qt的知识是不够的(更别说有些人还没用过Qt呢),多线程的知识更加重要。 而且单靠一两篇简单的文章是学不明白的,你看人家多线程编程能写一大章书呢!好了,强调一下背景知识的重要性,下面要提供一个最简单的Qt多线程的例子程序,它的功能是打开多个图片文件之后创建线程执行图片缩放的操作,创建图片的缩略图,完成后子线程会把缩略图传给GUI线程,由GUI线程创建Label控件来显示缩略图。各位看官注意了,这个例子很简单,和多线程相关的部分其实很少,没有用到Mutex也没涉及并发等等概念, 单一的演示如何防止大数据量的计算阻塞GUI线程。
例子的源码见附件,这里先列出一些关键的流程和代码, 方便大家理解:
第一步:打开图片文件
用QFileDialog::getOpenFileNames获得图片文件名的StringList
第二步:遍历List创建线程
- void MainWin::createThumbnail(const QString& filename)
- {
- QThread* thread = new ThumbnailThread(filename, 10 - waitseconds);
- connect(thread, SIGNAL(thumbnailFinished(QImage)), this, SLOT(addThumbnail(QImage)));
- connect(thread, SIGNAL(thumbnailFailed(const QString)), this, SLOT(showError(const QString)));
- connect(thread, SIGNAL(finished()), this, SLOT(deleteThread()));
- thread->start();
- }
finished是QThread类自带的信号,在线程结束执行时发出; 其他两个信号是自定义信号, 用于在GUI线程和子线程之间传递数据。
第三步:子线程内创建缩略图
使用了QImage的scaled方法做图片缩放。
- void ThumbnailThread::run()
- {
- if( bigpm.isNull())
- {
- emit thumbnailFailed(pmfilename);
- }
- else
- {
- smallpm = bigpm.scaled(TN_WIDTH, TN_HEIGHT, Qt::KeepAspectRatio);
- emit thumbnailFinished(smallpm);
- }
- }
第四步:GUI线程处理thumbnailFinished信号
为每个图片创建一个Label, 将之加到预先定义好的GridLayout中
- void MainWin::addThumbnail(QImage smallpm)
- {
- static int i = 0;
- static int j = 0;
- QLabel* label = new QLabel;
- label->setPixmap(QPixmap::fromImage(smallpm));
- QGridLayout* gl = qobject_cast(previewwidget->layout());
- gl->addWidget(label, j, i);
- label->show();
- qWarning() << "Label:" <isVisible();
- i++;
- if( i > previewwidget->width() / smallpm.width())
- {
- i = 0;
- j ++;
- }
- }
关键点提示:
Qt的signal可跨线程传递, 但要注意slot执行的线程。 connect时可以在第五个参数的位置指定连接的属性(这里用了默认值), 如DirectConnection表示在connect函数执行的线程里执行slot, QueuedConnection表示slot执行在接受者所在的线程,(还有其他选项参看文档)本例中的slot都执行在GUI线程中。
在收到finished信号后要清除执行完毕的线程, 为了防止删除后对线程实例的访问这里用了个deleteLater方法 — 在大家都不再访问thread实例时再删除。
Qt中所有和GUI相关的操作都要放在GUI线程里执行,包括所有Widget的创建和访问,QPixmap的类也算GUI的类, 所以本例中只能用QImage来处理图片。
这个例子其实用一个子线程就够了,给每张图片创建一个线程有点浪费。例子里有些为了测试需要加的代码可能会影响阅读(比如那个waitseconds),请大家自动忽略。 比如为了更好的看到效果,给线程里加了个sleep延缓处理图片的速度…
原文链接:http://www.cuteqt.com/blog/?p=547
小结:关于实例操作 Qt 多线程 后台创建缩略图 的内容介绍完了,希望本文对你有所帮助!更多关于多线程的内容请参考编辑推荐。