需求:异步执行两组任务,但第二组任务需要第一组完成之后才能执行。

本文详细介绍了如何利用 GCD 中的 dispatch_barrier_async 函数来创建一个'栅栏',确保在并发队列中,所有在栅栏前的任务执行完毕后,再执行栅栏后的任务。通过示例代码展示了其工作原理,同时提到了同步栅栏的使用方式,帮助开发者更好地理解和应用 GCD 在多任务调度中的能力。

dispatch_barrier_async 可以提供一个'栅栏'将两组异步执行的任务分隔开,保证先于栅栏的方法提交到队列的任务全部执行完成之后,开放栅栏,将栅栏之后的任务组调到队列中执行。

示例代码

- (void)barrier_display{
    
    NSLog(@"当前线程------%@",[NSThread currentThread]);
    
    dispatch_queue_t theConcurrentQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t theQueue = theConcurrentQueue;
    
    dispatch_async(theQueue, ^{
        
        NSLog(@"任务1开始");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1完成");
        
    });
    
    dispatch_async(theQueue, ^{
        
        NSLog(@"任务2开始");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2完成");
        
    });
    
    
    dispatch_barrier_async(theQueue, ^{
        NSLog(@"栅栏任务!!!");
    });
    
    dispatch_async(theQueue, ^{
        
        NSLog(@"任务3开始");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3完成");
        
    });
    
    dispatch_async(theQueue, ^{
        
        NSLog(@"任务4开始");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4完成");
        
    });
    
    
}

当前线程------<NSThread: 0x6000019e4080>{number = 1, name = main}

 任务1开始

任务2开始

任务1完成

任务2完成

 =======栅栏任务=============

 任务3开始

 任务4开始

 任务4完成

 任务3完成

看源码

void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
	dispatch_continuation_t dc = _dispatch_continuation_alloc();
	uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
	dispatch_qos_t qos;

	qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
	_dispatch_continuation_async(dq, dc, qos, dc_flags);
}

将改成同步栅栏+并发队列也可以实现

 

dispatch_barrier_sync(theQueue, ^{
        NSLog(@"=======栅栏任务=============");
    });

 

// main.cpp - 结构化数据对比工具(Qt4 兼容版 + 线程池支持) // 本程序用于比较两组结构化文本数据(如 CSV 格式)中的对象差异 // 支持 Person 和 Product 两种类型,并使用 QThreadPool 避免界面阻塞 #include // QApplication:管理整个应用程序的核心,包括事件循环、窗口系统等 #include // QWidget:所有 GUI 控件的基类,主窗口继承自它 #include // QVBoxLayout:垂直布局容器,自动将子控件从上到下排列 #include // QHBoxLayout:水平布局容器,自动将子控件从左到右排列 #include // QLabel:显示不可编辑的文字标签,例如说明文字 #include // QTextEdit:多行文本输入/输出框,可用于显示或输入大段文本 #include // QPushButton:可点击的按钮控件,用户通过点击触发操作 #include // QComboBox:下拉选择框,允许用户在多个选项中选择一个 #include // QSplitter:分割窗口区域,用户可以用鼠标拖动调整左右/上下大小 #include // QMessageBox:弹出标准对话框(信息提示、警告、错误等) #include // QFileDialog:打开或保存文件的标准图形界面对话框 #include // QFile:对本地文件进行读写操作的基础类 #include // QTextStream:以流的方式读写文本内容,支持设置编码格式 #include // QDateTime:获取当前时间并格式化为字符串(如 yyyy-MM-dd HH:mm:ss) #include // QClipboard:访问系统剪贴板,实现复制粘贴功能 #include // QThread:提供线程相关功能,用于调试输出当前线程 ID #include // qDebug():打印调试信息到控制台,比 std::cout 更安全跨平台 // 必须包含 QTextCodec 来处理中文字符编码(尤其在 Qt4 中) #include // 多线程相关头文件 #include // QThreadPool:全局线程池,自动管理和复用工作线程 #include // QRunnable:表示一个可在子线程中运行的任务接口 #include // (未直接使用)但常配合 QtConcurrent 使用,监听异步任务状态 #include // QObject:信号与槽机制的基础类,也是 invokeMethod 所需 // C++ 标准库头文件 #include // std::shared_ptr:智能指针,自动管理动态分配的对象生命周期 #include // std::map:基于红黑树的键值对容器,支持按 key 快速查找 #include // std::vector:动态数组,用于存储一组对象指针 #include // std::string:C++ 标准字符串类型,用于保存字段内容 #include // std::stringstream:字符串流,用于将一行 CSV 拆分为多个字段 #include // std::atoi, std::atof:C 风格函数,将字符串转为整数或浮点数 #include // std::fabs:浮点数绝对值函数,用于判断两个 double 是否“近似相等” // ==================== 抽象数据对象接口 ==================== // 所有具体的数据结构(如 Person、Product)都必须继承此类 class DataObject { public: // 定义别名 Ptr,简化 shared_ptr 的书写 typedef std::shared_ptr Ptr; // 虚析构函数:确保通过基类指针删除派生类对象时能正确调用其析构函数 virtual ~DataObject() {} // 获取该对象的唯一标识键(例如 ID 或 SKU),用于匹配 A 和 B 中的同一条记录 virtual std::string getKey() const = 0; // 将对象转换为易读的字符串表示形式,用于输出显示 virtual std::string toString() const = 0; // 比较当前对象与另一个对象之间的差异,返回修改字段描述 virtual std::string diff(const DataObject &other) const = 0; // 克隆方法:返回当前对象的一个深拷贝副本(实现多态复制) virtual Ptr clone() const = 0; }; // ==================== 示例结构体 1: Person ==================== // 表示一个人的信息:ID、姓名、年龄 struct Person : DataObject { int id; // 唯一编号 std::string name; // 姓名 int age; // 年龄 // 构造函数:从一行 CSV 文本创建 Person 对象 explicit Person(const std::string &line) : id(0), age(0) { std::stringstream ss(line); // 创建字符串流来逐个提取字段 std::string field; // 临时变量保存当前字段内容 if (std::getline(ss, field, ',')) id = atoi(field.c_str()); // 第一个字段是 id,使用 atoi 转成整数 if (std::getline(ss, field, ',')) name = field; // 第二个字段是 name,直接赋值 if (std::getline(ss, field, ',')) age = atoi(field.c_str()); // 第三个字段是 age,转成整数 } // 默认构造函数:初始化为空对象 Person() : id(0), age(0) {} // 实现 getKey() 方法:返回唯一的键 "Person:ID" std::string getKey() const { return "Person:" + std::to_string(id); } // 实现 toString() 方法:生成可读的对象描述 std::string toString() const { return "Person{id=" + std::to_string(id) + ", name='" + name + "'" + ", age=" + std::to_string(age) + "}"; } // 实现 diff() 方法:比较当前对象与另一个 Person 是否有不同字段 std::string diff(const DataObject &other) const { const Person& p = static_cast<const Person&>(other); // 安全类型转换(已知类型) std::string changes; // 存储变化信息 if (name != p.name) { changes += " name: '" + name + "' -> '" + p.name + "';"; } if (age != p.age) { changes += " age: " + std::to_string(age) + " -> " + std::to_string(p.age) + ";"; } // 如果没有任何变化,返回“(无变化)”提示 return changes.empty() ? "(无变化)" : changes; } // 实现 clone() 方法:返回一个新的 Person 副本(深拷贝) Ptr clone() const { return std::make_shared<Person>(*this); // 使用智能指针包装新对象 } }; // ==================== 示例结构体 2: Product ==================== // 表示商品信息:SKU 编号、标题、价格 struct Product : DataObject { std::string sku; // 商品唯一编号 std::string title; // 商品名称 double price; // 商品价格(浮点数) // 构造函数:从一行 CSV 文本创建 Product 对象 explicit Product(const std::string &line) : price(0.0) { std::stringstream ss(line); std::string field; if (std::getline(ss, field, ',')) sku = field; // 第一列:SKU if (std::getline(ss, field, ',')) title = field; // 第二列:title if (std::getline(ss, field, ',')) price = atof(field.c_str()); // 第三列:price,使用 atof 转换 } // 默认构造函数 Product() : price(0.0) {} // 返回唯一键:“Product:SKU” std::string getKey() const { return "Product:" + sku; } // 易读格式输出(价格保留两位小数) std::string toString() const { char buf[100]; snprintf(buf, sizeof(buf), "%.2f", price); // 格式化价格 return "Product{sku='" + sku + "', title='" + title + "', price=" + std::string(buf) + "}"; } // 比较两个 Product 是否有字段不同 std::string diff(const DataObject &other) const { const Product& p = static_cast<const Product&>(other); std::string changes; if (title != p.title) { changes += " title: '" + title + "' -> '" + p.title + "';"; } if (fabs(price - p.price) > 1e-5) { // 浮点数不能直接 == 比较,需用误差判断 char oldPrice[20], newPrice[20]; snprintf(oldPrice, sizeof(oldPrice), "%.2f", price); snprintf(newPrice, sizeof(newPrice), "%.2f", p.price); changes += " price: " + std::string(oldPrice) + " -> " + std::string(newPrice) + ";"; } return changes.empty() ? "(无变化)" : changes; } // 返回克隆对象(深拷贝) Ptr clone() const { return std::make_shared<Product>(*this); } }; // ==================== 数据解析器工厂 ==================== // 静态类:根据用户选择的类型将文本解析为对象列表 class DataParser { public: // 定义别名:一组数据对象的列表 typedef std::vectorDataObject::Ptr ObjectList; // 静态方法:将输入文本按指定类型解析成对象列表 static ObjectList parse(const QString &text, const QString &type) { ObjectList result; // 存放成功解析的对象 QStringList lines = text.split('\n', QString::SkipEmptyParts); // 按换行拆分,跳过空行 // 遍历每一行文本 for (int i = 0; i < lines.size(); ++i) { const QString &line = lines[i]; // 当前行 try { if (type == "Person") { // 解析为 Person 类型并加入结果集 result.push_back(std::make_shared<Person>(line.toStdString())); } else if (type == "Product") { // 解析为 Product 类型 result.push_back(std::make_shared<Product>(line.toStdString())); } else { // 不支持的类型,打印调试日志 qWarning("未知类型: %s", type.toLocal8Bit().data()); } } catch (...) { // 捕获任何异常(比如数字转换失败) // 注意:这里不能弹 QMessageBox(非主线程禁止操作 GUI) // 我们改为记录错误并在主线程提示 qDebug() << "[Worker] 解析失败行:" << line; } } return result; // 返回解析完成的对象列表 } }; // ==================== 异步任务类:继承 QRunnable ==================== // 表示一个异步加载并解析文件的任务 class LoadDataTask : public QRunnable { public: // 枚举标识是哪个输入源(A 左侧 / B 右侧) enum Source { SourceA, SourceB }; // 构造函数:传入文件路径、数据类型、来源(A 或 B)、接收结果的对象 LoadDataTask(const QString &filePath, const QString &dataType, Source src, QObject *receiver) : m_filePath(filePath), m_dataType(dataType), m_source(src), m_receiver(receiver) {} // run() 是 QRunnable 的纯虚函数,会被线程池自动调用,在子线程中执行 void run() override { // 输出调试信息:正在处理哪个文件、运行在线程几 qDebug() << "正在子线程中处理文件:" << m_filePath << " | 线程ID:" << QThread::currentThreadId(); // 1. 读取文件内容 QFile file(m_filePath); // 创建文件对象 QString content; // 用于存储读取的全部文本 bool success = false; // 记录是否成功 QString error; // 存储错误信息(如果失败) // 尝试以只读文本模式打开文件 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); // 创建文本流 stream.setCodec(QTextCodec::codecForName("UTF-8")); // 设置编码为 UTF-8(支持中文) content = stream.readAll(); // 一次性读取所有内容 file.close(); // 关闭文件 success = true; // 标记成功 } else { error = "无法打开文件"; // 设置错误消息 } // 2. 如果读取成功,则进行解析(也在子线程) std::vector<DataObject::Ptr> parsedData; if (success) { parsedData = DataParser::parse(content, m_dataType); // 调用解析器解析文本 } // 3. 准备结果对象,封装所有需要传回主线程的数据 AsyncTaskResult result; result.success = success; // 是否成功 result.content = content; // 文件原始内容(用于显示在 QTextEdit) result.parsedData = parsedData; // 解析后的对象列表(用于后续对比) result.source = m_source; // 来源是 A 还是 B result.errorMsg = error; // 错误信息(如果有) result.filePath = m_filePath; // 文件路径(用于错误提示) // 4. 使用元对象系统跨线程发送信号通知主线程 // Qt::QueuedConnection 确保槽函数在目标线程(GUI 主线程)排队执行 QMetaObject::invokeMethod(m_receiver, "onTaskFinished", Qt::QueuedConnection, Q_ARG(AsyncTaskResult, result)); } private: QString m_filePath; // 要加载的文件路径 QString m_dataType; // 当前选择的数据类型(“Person” 或 “Product”) Source m_source; // 来源标识(A 或 B) QObject *m_receiver; // 接收结果的对象(即主窗口 this) // 内部结构体:封装任务执行结果,便于跨线程传递 struct AsyncTaskResult { bool success; // 成功标志 QString content; // 文件内容(QString) std::vector<DataObject::Ptr> parsedData; // 解析后的对象列表 Source source; // 来源 A/B QString errorMsg; // 错误信息 QString filePath; // 文件路径 }; // 声明该结构体支持 Qt 元对象系统序列化(让 invokeMethod 能传递它) Q_DECLARE_METATYPE(AsyncTaskResult) }; // 手动注册类型到 Qt 元对象系统(必须放在类外) Q_DECLARE_METATYPE(LoadDataTask::AsyncTaskResult) // ==================== 主窗口类声明 ==================== // 继承自 QWidget,作为主界面窗口 class StructuredDataComparator : public QWidget { Q_OBJECT // 必须添加:启用 Qt 的元对象系统(信号槽、moc 工具需要) public: // 构造函数:parent 表示父窗口,默认为空 explicit StructuredDataComparator(QWidget *parent = 0); private slots: // 槽函数:响应按钮点击事件 void onCompare(); // 开始对比 A 和 B 的数据 void onClear(); // 清空所有输入输出内容 void onCopyResult(); // 复制对比结果到系统剪贴板 void onLoadFileA(); // 加载文件 A 到左侧输入框 void onLoadFileB(); // 加载文件 B 到右侧输入框 void onTaskFinished(const LoadDataTask::AsyncTaskResult &result); // 接收子线程完成的结果 private: // 私有方法:初始化用户界面布局和控件 void setupUI(); // 成员变量:GUI 控件指针 QTextEdit *inputA; // 左侧输入框(旧数据) QTextEdit *inputB; // 右侧输入框(新数据) QTextEdit *outputArea; // 下方输出区域(显示对比结果) QPushButton *compareBtn; // “开始对比”按钮 QPushButton *clearBtn; // “清空”按钮 QPushButton *copyBtn; // “复制结果”按钮 QComboBox *dataTypeCombo; // 下拉框选择数据类型(Person / Product) // 缓存最后一次加载成功的解析数据(用于对比,避免重复解析) std::vector<DataObject::Ptr> m_lastParsedA; std::vector<DataObject::Ptr> m_lastParsedB; }; // ========== 主窗口构造函数实现 ========== StructuredDataComparator::StructuredDataComparator(QWidget *parent) : QWidget(parent) // 调用父类 QWidget 的构造函数 { // 注册自定义类型,使它可以跨线程传递(由 QMetaObject::invokeMethod 使用) qRegisterMetaTypeLoadDataTask::AsyncTaskResult(“LoadDataTask::AsyncTaskResult”); setupUI(); // 初始化界面布局和控件 } // ========== setupUI 函数实现:构建整个用户界面 ========== void StructuredDataComparator::setupUI() { setWindowTitle(“🧾 结构化数据对比工具(多线程安全)”); // 设置窗口标题 resize(900, 700); // 设置初始窗口大小(宽900px,高700px) QVBoxLayout *mainLayout = new QVBoxLayout(this); // 创建主垂直布局,作为顶层容器 // --- 第一部分:顶部控件 —— 选择数据类型 --- QHBoxLayout *topLayout = new QHBoxLayout; // 水平布局放置标签和下拉框 topLayout->addWidget(new QLabel("数据类型:")); // 添加说明文字 dataTypeCombo = new QComboBox; // 创建下拉选择框 dataTypeCombo->addItem("Person"); // 添加选项:Person dataTypeCombo->addItem("Product"); // 添加选项:Product topLayout->addWidget(dataTypeCombo); // 将下拉框加入布局 topLayout->addStretch(); // 右侧留白,使控件靠左排列 mainLayout->addLayout(topLayout); // 将顶部布局加入主布局 // --- 第二部分:左右输入区(使用分割器)--- QSplitter *splitter = new QSplitter(Qt::Horizontal); // 创建水平分割器,左右可拖动 // 左侧容器:旧数据输入 QWidget *containerA = new QWidget; // 包裹左侧控件的容器 QVBoxLayout *layoutA = new QVBoxLayout; // 垂直布局 layoutA->addWidget(new QLabel("来源 A(旧数据)")); // 添加标签说明 inputA = new QTextEdit; // 创建多行文本框 inputA->setPlaceholderText("输入格式:\nid,name,age\n1,Alice,30"); // 占位提示文本 layoutA->addWidget(inputA); // 将文本框加入布局 containerA->setLayout(layoutA); // 容器应用此布局 // 右侧容器:新数据输入 QWidget *containerB = new QWidget; QVBoxLayout *layoutB = new QVBoxLayout; layoutB->addWidget(new QLabel("来源 B(新数据)")); inputB = new QTextEdit; inputB->setPlaceholderText("输入格式:\nid,name,age\n2,Bob,25"); layoutB->addWidget(inputB); containerB->setLayout(layoutB); // 将左右两个容器加入分割器 splitter->addWidget(containerA); splitter->addWidget(containerB); mainLayout->addWidget(splitter); // 将分割器加入主布局 // --- 第三部分:按钮行 --- QHBoxLayout *btnLayout = new QHBoxLayout; // 水平布局放置按钮 QPushButton *loadBtnA = new QPushButton("📁 加载 A"); // 加载文件 A 的按钮 QPushButton *loadBtnB = new QPushButton("📁 加载 B"); // 加载文件 B 的按钮 compareBtn = new QPushButton("🔍 开始对比"); // 开始对比按钮 clearBtn = new QPushButton("🗑️ 清空"); // 清空按钮 copyBtn = new QPushButton("📋 复制结果"); // 复制结果按钮 // 按顺序添加按钮到布局 btnLayout->addWidget(loadBtnA); btnLayout->addWidget(loadBtnB); btnLayout->addSpacing(40); // 插入空白间距,美观分隔 btnLayout->addWidget(compareBtn); btnLayout->addWidget(clearBtn); btnLayout->addWidget(copyBtn); mainLayout->addLayout(btnLayout); // 将按钮行加入主布局 // --- 第四部分:输出结果显示区 --- outputArea = new QTextEdit; // 创建输出文本框 outputArea->setReadOnly(true); // 设置只读,防止用户误改 outputArea->setPlaceholderText("对比结果将显示在这里..."); // 提示语 mainLayout->addWidget(outputArea); // 加入主布局 // === 最后:连接信号与槽(使用 Qt4 宏语法)=== connect(compareBtn, SIGNAL(clicked()), this, SLOT(onCompare())); // 点击“开始对比”执行 onCompare connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear())); // 点击“清空”执行 onClear connect(copyBtn, SIGNAL(clicked()), this, SLOT(onCopyResult())); // 点击“复制”执行 onCopyResult connect(loadBtnA, SIGNAL(clicked()), this, SLOT(onLoadFileA())); // 点击“加载 A”执行 onLoadFileA connect(loadBtnB, SIGNAL(clicked()), this, SLOT(onLoadFileB())); // 点击“加载 B”执行 onLoadFileB } // ========== 槽函数:执行数据对比逻辑 ========== void StructuredDataComparator::onCompare() { QString type = dataTypeCombo->currentText(); // 获取当前选择的数据类型 QString textA = inputA->toPlainText(); // 获取左侧输入的所有文本 QString textB = inputB->toPlainText(); // 获取右侧输入的所有文本 // 检查是否至少有一个输入为空 if (textA.trimmed().isEmpty() || textB.trimmed().isEmpty()) { outputArea->setText("⚠️ 请确保两个输入框都有数据!"); // 提示用户 return; // 提前退出 } // 使用已缓存的 parsed 数据(由线程任务填充) std::vector<DataObject::Ptr> listA = m_lastParsedA; std::vector<DataObject::Ptr> listB = m_lastParsedB; // 如果没有缓存且文本不为空,则临时同步解析一次(适用于手动粘贴的小数据) if (listA.empty() && !textA.isEmpty()) { listA = DataParser::parse(textA, type); } if (listB.empty() && !textB.isEmpty()) { listB = DataParser::parse(textB, type); } // 创建 map:key -> object,便于 O(log n) 查找 std::map<std::string, DataObject::Ptr> mapA, mapB; for (size_t i = 0; i < listA.size(); ++i) mapA[listA[i]->getKey()] = listA[i]; for (size_t i = 0; i < listB.size(); ++i) mapB[listB[i]->getKey()] = listB[i]; // 定义三个向量分别存储新增、删除、修改的对象描述 std::vector<std::string> added; std::vector<std::string> removed; std::vector<std::string> modified; // 在 A 中但不在 B 中 → 被删除 for (std::map<std::string, DataObject::Ptr>::iterator it = mapA.begin(); it != mapA.end(); ++it) { if (mapB.find(it->first) == mapB.end()) { removed.push_back(it->second->toString()); } } // 在 B 中但不在 A 中 → 新增 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { if (mapA.find(it->first) == mapA.end()) { added.push_back(it->second->toString()); } } // 同时存在于 A 和 B → 检查是否修改 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { std::string key = it->first; std::map<std::string, DataObject::Ptr>::iterator oldIt = mapA.find(key); if (oldIt != mapA.end()) { std::string diffStr = it->second->diff(*oldIt->second); if (diffStr != "(无变化)") { modified.push_back("🔹 " + key + "\n 修改: " + diffStr); } } } // 生成最终报告文本 QString result; result += "🧾 结构化数据对比报告\n"; result += "==============================\n"; result += QString("📅 时间: %1\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); result += QString("📊 类型: %1\n\n").arg(type); result += QString("🆕 新增 (%1):\n").arg(added.size()); for (size_t i = 0; i < added.size(); ++i) { result += " • " + QString::fromStdString(added[i]) + "\n"; } result += "\n"; result += QString("🗑️ 删除 (%1):\n").arg(removed.size()); for (size_t i = 0; i < removed.size(); ++i) { result += " • " + QString::fromStdString(removed[i]) + "\n"; } result += "\n"; result += QString("✏️ 修改 (%1):\n").arg(modified.size()); for (size_t i = 0; i < modified.size(); ++i) { result += QString::fromStdString(modified[i]) + "\n"; } // 显示在输出框 outputArea->setText(result); } // ========== 清空所有内容 ========== void StructuredDataComparator::onClear() { inputA->clear(); // 清空左侧输入框 inputB->clear(); // 清空右侧输入框 outputArea->clear(); // 清空输出框 m_lastParsedA.clear(); // 清除缓存的解析结果 m_lastParsedB.clear(); } // ========== 复制结果到剪贴板 ========== void StructuredDataComparator::onCopyResult() { // 获取 QApplication 的全局剪贴板对象,并设置当前输出文本 QApplication::clipboard()->setText(outputArea->toPlainText()); // 弹出成功提示对话框 QMessageBox::information(this, “已复制”, “结果已复制到剪贴板!”); } // ========== 加载文件 A(启动线程任务)========== void StructuredDataComparator::onLoadFileA() { // 弹出文件选择对话框,过滤 .csv 和 .txt 文件 QString filePath = QFileDialog::getOpenFileName(this, “加载 A 文件”, “”, “CSV/TXT (*.csv *.txt)”); if (!filePath.isEmpty()) { // 用户未取消选择 // 创建任务:传入文件路径、当前类型、来源 A、接收者(this) LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceA, this); task->setAutoDelete(true); // 任务完成后由线程池自动 delete,避免内存泄漏 // 提交到全局线程池执行(run() 将在后台线程被调用) QThreadPool::globalInstance()->start(task); // 显示加载中提示(用户体验优化) inputA->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 加载文件 B ========== void StructuredDataComparator::onLoadFileB() { QString filePath = QFileDialog::getOpenFileName(this, “加载 B 文件”, “”, “CSV/TXT (*.csv *.txt)”); if (!filePath.isEmpty()) { // 创建任务:来源为 B LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceB, this); task->setAutoDelete(true); QThreadPool::globalInstance()->start(task); // 显示加载中提示 inputB->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 接收子线程完成的结果(主线程执行)========== void StructuredDataComparator::onTaskFinished(const LoadDataTask::AsyncTaskResult &result) { // 此函数在主线程执行,可以安全地更新 UI qDebug() << “[MainThread] 收到任务完成通知,来源:” << (result.source == LoadDataTask::SourceA ? “A” : “B”) << " | 线程ID:" << QThread::currentThreadId(); if (result.success) { // 更新对应的输入框文本 if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(result.content); // 显示文件内容 m_lastParsedA = result.parsedData; // 缓存解析结果供对比使用 } else { inputB->setPlainText(result.content); m_lastParsedB = result.parsedData; } // 提示用户加载成功 QMessageBox::information(this, "完成", "文件加载并解析成功!"); } else { // 加载失败则显示错误信息 QString msg = "加载失败:" + result.errorMsg + "\n文件:" + result.filePath; if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(msg); } else { inputB->setPlainText(msg); } // 弹出错误对话框 QMessageBox::critical(this, "错误", msg); } } // ========== MOC 编译支持(必须)========== // Qt 的元对象编译器(moc)需要处理含有 Q_OBJECT 的类 // 此行告诉 moc 处理这个文件中的信号槽 #include “main.moc” // ========== 程序入口点 ========== int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建 Qt 应用对象,管理事件循环 #if QT_VERSION < 0x050000 // Qt4 推荐:设置默认字符串编码为 UTF-8,以便正确显示中文 QTextCodec::setCodecForCStrings(QTextCodec::codecForName(“UTF-8”)); #endif StructuredDataComparator tool; // 创建主窗口实例 tool.show(); // 显示窗口 return app.exec(); // 启动事件循环,等待用户交互 } 这段代码在加入了多线程处理之后,可能是将代码进行头文件和源文件分离时出现了问题,请帮我将这段代码按头文件和源文件的方式进行分离,并说明引用关系
最新发布
09-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值