在一开始写需求时,就决定在文件搜索时使用多线程了,不过当时想的是多线程搜索文件,而不是像这里一样:主线程(GUI 线程)主要负责处理用户界面和用户交互,搜索线程负责文件搜索逻辑。
多线程搜索文件:这个后面还是要实现的,先埋个坑
0722 更新:已经实现了,当时搜索到大量文件时会导致UI卡顿严重,初步猜测原因为线程之间交流频繁导致的
1. 为什么使用多线程
在开发 GUI 应用程序时,长时间运行的任务(如文件搜索)可能会导致界面冻结,影响用户体验。为了解决这个问题,我们可以使用多线程技术,将耗时的操作放到单独的线程中执行,从而保持用户界面的响应性。本文将详细介绍如何在 Qt 应用中实现多线程文件搜索。
2. 涉及的线程
在这个实现中,主要涉及两个线程:
- 主线程(GUI 线程):负责处理用户界面和用户交互。
- 文件搜索线程(工作线程):负责执行耗时的文件搜索操作。
3. 具体实现步骤
3.1 创建文件搜索线程类
我们创建了一个名为 FileSearchThread
的类,继承自 QThread
,用于在单独的线程中执行文件搜索操作。
文件:FileSearchThread.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #ifndef FILESEARCHTHREAD_H #define FILESEARCHTHREAD_H
#include <QThread> #include <QString> #include <QDirIterator>
class FileSearchThread : public QThread { Q_OBJECT
public: FileSearchThread(const QString &keyword, const QString &path, QObject *parent = nullptr);
void run() override;
signals: void fileFound(const QString &filePath); void searchFinished();
private: QString searchKeyword; QString searchPath; };
#endif
|
文件:FileSearchThread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include "FileSearchThread.h"
FileSearchThread::FileSearchThread(const QString &keyword, const QString &path, QObject *parent) : QThread(parent), searchKeyword(keyword), searchPath(path) {}
void FileSearchThread::run() { QDirIterator it(searchPath, QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { QString filePath = it.next(); if (filePath.contains(searchKeyword, Qt::CaseInsensitive)) { emit fileFound(filePath); } } emit searchFinished(); }
|
3.2 修改 FileSearch
类
在 FileSearch
类中,我们添加了信号和槽机制,以处理文件搜索线程的结果。
文件:FileSearch.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #ifndef FILESEARCH_H #define FILESEARCH_H
#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QListWidget> #include "CustomModel.h"
namespace Ui { class FileSearch; }
class FileSearch : public QWidget { Q_OBJECT
public: explicit FileSearch(QWidget *parent = nullptr); ~FileSearch();
private slots: void onSearchButtonClicked(); void onFileFound(const QString &filePath); void onSearchFinished();
private: Ui::FileSearch *ui; CustomModel *resultModel; QPushButton *searchButton; QLineEdit *searchLineEdit; QLineEdit *pathLineEdit; QListWidget *resultListWidget; };
#endif
|
文件:FileSearch.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include "FileSearch.h" #include "ui_FileSearch.h" #include "Logger.h" #include "FileSearchThread.h" #include <QDir> #include <QDirIterator> #include <QVBoxLayout> #include <QDebug> #include <QCoreApplication>
FileSearch::FileSearch(QWidget *parent) : QWidget(parent), ui(new Ui::FileSearch), resultModel(new CustomModel(this)) { ui->setupUi(this);
searchButton = ui->searchButton; searchLineEdit = ui->searchLineEdit; pathLineEdit = ui->pathLineEdit; resultListWidget = ui->resultListWidget;
connect(searchButton, &QPushButton::clicked, this, &FileSearch::onSearchButtonClicked);
qDebug() << "List view model set.";
if (!layout()) { QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(resultListWidget); setLayout(layout); } }
FileSearch::~FileSearch() { delete ui; }
void FileSearch::onSearchButtonClicked() { QString searchKeyword = searchLineEdit->text(); QString searchPath = pathLineEdit->text();
if (searchPath.isEmpty()) { searchPath = QDir::rootPath(); }
resultListWidget->clear(); Logger::instance().log("Search started for keyword: " + searchKeyword + " in path: " + searchPath);
FileSearchThread *searchThread = new FileSearchThread(searchKeyword, searchPath, this); connect(searchThread, &FileSearchThread::fileFound, this, &FileSearch::onFileFound); connect(searchThread, &FileSearchThread::searchFinished, this, &FileSearch::onSearchFinished); searchThread->start(); }
void FileSearch::onFileFound(const QString &filePath) { resultListWidget->addItem(filePath); Logger::instance().log("Found file: " + filePath);
resultListWidget->reset(); resultListWidget->update(); resultListWidget->viewport()->update(); QCoreApplication::processEvents(); }
void FileSearch::onSearchFinished() { if (resultListWidget->count() > 0) { resultListWidget->scrollToItem(resultListWidget->item(0)); } }
#include "filesearch.moc"
|
主要看一下这几行代码,
1 2 3 4 5
| FileSearchThread *searchThread = new FileSearchThread(searchKeyword, searchPath, this); connect(searchThread, &FileSearchThread::fileFound, this, &FileSearch::onFileFound); connect(searchThread, &FileSearchThread::searchFinished, this, &FileSearch::onSearchFinished); searchThread->start();
|
创建和启动文件搜索线程:
FileSearchThread
是一个自定义线程类,继承自 QThread
,用于在单独的线程中执行文件搜索操作。构造函数传递了搜索关键字、路径和父对象。
connect
函数连接了 FileSearchThread
的信号和 FileSearch
的槽函数,以处理线程完成搜索时发出的信号。
- 调用
start
方法启动线程,这会调用 FileSearchThread
的 run
方法在新线程中执行文件搜索操作
4. 多线程相关技术要点
- QThread 的使用
QThread
是 Qt 提供的线程类,用于创建和管理线程。通过继承 QThread
并重写 run
方法,可以在子类中定义线程的工作内容。需要注意的是,run
方法中的代码会在新的线程中执行,而不是在主线程中。以下是一些使用 QThread 的注意事项:
- 避免直接操作 GUI:在
run
方法中不要直接操作 GUI 元素,所有的 GUI 操作应通过信号和槽在主线程中完成。
- 正确处理线程结束:在需要的地方调用
wait
方法,确保线程结束后再继续执行其他操作,避免资源泄露。
- 线程优先级:可以通过
setPriority
方法设置线程的优先级,确保重要任务获得更多 CPU 时间。
- 信号和槽机制
Qt 的信号和槽机制是实现线程间通信的关键。通过在工作线程中发射信号,并在主线程中连接相应的槽函数,可以实现线程间的数据传递和同步。以下是信号和槽的定义和使用:
- 信号定义:在
FileSearchThread
类中定义了 fileFound
和 searchFinished
信号,用于通知主线程文件搜索的进度和完成状态。
- 槽函数:在
FileSearch
类中定义了 onFileFound
和 onSearchFinished
槽函数,用于处理文件搜索线程发射的信号。
- 自动连接机制:使用
connect
函数时,可以利用 Qt 的自动连接机制,基于命名约定自动连接信号和槽。
- 线程安全
在多线程环境中,确保线程安全是非常重要的。Qt 提供了一些机制来帮助实现线程安全:
- QMutex:互斥锁,用于保护共享资源,防止多个线程同时访问导致数据不一致。
- QWaitCondition:条件变量,用于线程间的同步,通常与
QMutex
一起使用。
- QAtomic:提供了一些原子操作,用于实现无锁的线程安全操作,适用于简单的计数或标志位。
在本例中,由于文件搜索操作主要是读操作,没有涉及共享资源的写操作,因此不需要使用 QMutex
。但在复杂的多线程应用中,使用 QMutex
和 QWaitCondition
等机制来确保线程安全是必不可少的。
- 线程生命周期管理
在创建和使用线程时,必须注意线程的生命周期管理。以下是一些关键点:
- 线程启动:通过调用
start
方法启动线程。
- 线程结束:线程完成任务后会自动结束,可以通过连接
searchFinished
信号来处理线程结束后的操作。
- 资源释放:确保在适当的时候释放线程对象,以避免内存泄漏。在本例中,
FileSearchThread
对象是由 FileSearch
类管理的,因此在 FileSearch
类的析构函数中需要释放相关资源。