PyQt5多线程开发如何实现双线程执行完毕后再触发目标函数
你现在示例代码的写法是错的:Qt里调用线程的start()方法启动线程是异步操作——相当于你派两个人出去干活,刚给人布置完任务转身就去要结果,这时候两个人还在路上,function()会在两个线程都没跑完的时候就执行,根本拿不到两个线程的返回参数。
这个需求逻辑其实和「等两个外卖都送到了再开饭」一模一样:两个线程就是两个骑手,各自跑完任务会发个信号说「我送到了」,你只要记清楚已经到了几个骑手,等两个都到齐了,再执行目标函数就行。
1. 先给两个线程加完成信号
先确保两个线程执行完自己的任务时,会发出带返回参数的完成信号,比如约定命名为sig_thread1_finished(参数1)、sig_thread2_finished(参数2),线程跑到最后一步就用emit把这个信号发出来,把要传的参数带上。
2. 给窗口类加3类成员变量
不要把变量都写成局部的,要在你写按钮点击事件的类(一般是主窗口类)里加下面这几个成员:
- 1个计数器,初始值为0,用来记录已经跑完的线程数量
- 2个存储变量,类型和两个线程信号传的参数一致,专门存两个线程返回的结果
- 2个线程对象本身,不要写成按钮点击函数里的局部变量,不然函数执行完线程就被销毁了,会直接崩溃
举个例子,如果两个线程返回的都是字符串类型,代码就这么写:
private: int m_finish_count = 0; // 已完成线程计数 QString m_param_from_t1; // 存线程1的返回参数 QString m_param_from_t2; // 存线程2的返回参数 Thread1* thread_1; // 线程1对象,为你自定义的线程类 Thread2* thread_2; // 线程2对象
3. 写中间槽函数做中转,不要直接连目标函数
千万不要直接把目标函数和两个线程的完成信号绑定——那样只要任意一个线程跑完,目标函数就会被触发一次,这时候另一个线程的参数还没传过来,结果肯定错。
你需要写两个小的中间槽,分别接两个线程的完成信号,每个槽干3件事:存当前线程传过来的参数、给计数器加1、检查是不是两个线程都跑完了。
示例代码:
// 接收线程1完成信号的槽 void MainWindow::on_thread1_finished(QString param) { m_param_from_t1 = param; m_finish_count++; checkAllThreadDone(); } // 接收线程2完成信号的槽 void MainWindow::on_thread2_finished(QString param) { m_param_from_t2 = param; m_finish_count++; checkAllThreadDone(); } // 检查所有线程是否完成的工具函数 void MainWindow::checkAllThreadDone() { if(m_finish_count != 2) { return; // 还有线程没跑完,直接返回不做任何操作 } // 走到这里说明两个线程都跑完了,调用目标函数,把存好的两个参数传进去 targetFunction(m_param_from_t1, m_param_from_t2); // 调用完重置计数器,方便下次点击按钮使用 m_finish_count = 0; }
4. 只绑定一次信号槽,按钮点击事件只做启动操作
注意:信号槽的连接不要写在按钮点击函数里,不然每点一次按钮就会多绑定一次,最后线程跑完目标函数会被触发N次。连接操作放在窗口类的构造函数里,只执行一次就够:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 初始化线程对象 thread_1 = new Thread1(this); thread_2 = new Thread2(this); // 绑定信号槽,只做一次 connect(thread_1, &Thread1::sig_thread1_finished, this, &MainWindow::on_thread1_finished); connect(thread_2, &Thread2::sig_thread2_finished, this, &MainWindow::on_thread2_finished); }
按钮点击事件里只需要做重置计数、启动两个线程的操作,绝对不要在这里直接调用目标函数:
void MainWindow::on_pushButton_clicked() { // 每次点击先把计数清零,避免上次运行的状态干扰 m_finish_count = 0; // 启动两个线程 thread_1->start(); thread_2->start(); }
- 别把线程对象定义成按钮点击函数里的局部变量:点击函数执行完就会回收局部变量,线程还没跑完就被销毁,直接崩溃
- 别重复绑定信号槽:
connect操作写在构造函数里一次就够,写在点击函数里会导致重复绑定 - 别忘重置计数器:每次点击按钮启动线程前、目标函数执行完后都要把计数清零,不然下次点击时计数错误,目标函数永远触发不了
- 别在线程里直接操作UI:Qt规定UI控件只能在主线程操作,线程里要改UI必须发信号给主线程处理
内容的提问来源于stack exchange,提问作者Ravi teja Banda

