问题描述
自己有个爬虫项目,使用 PySide6 只做 GUI,包装 Selenium,其中在 GUI 上会打印爬虫进度,见下图。
但当 Selenium 出现错误我重新点击开始的时候,发现 PySide6 crash 了!错误信息如下:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
问题调研
网上查阅资料知道这是因为
You're trying to read/write a file which is open. In this case, simply closing the file and rerunning the script solved the issue
即正在读/写正在打开的文件,解决方案就是关闭已经打开的文件就好,然后重新执行脚本。但问题是关闭哪个文件啊 😂
自定义信号
深入分析我的程序发现,我的自定义信号没有写好。
为什么需要自定义信号?当我们需要更新 UI 时,我们既不能在主线程直接操作(会阻塞 UI),又不能在子线程直接操作(会有意想不到的 BUG,比如我这个 crash)。当我们需要操作 UI,就需要发出一个自定义信号,主线程收到信号后,会在合适的时机尽快更新 UI。
首先,自定义信号代码如下
class MySignal(QObject):
# 定义更新日志的 signal
update_log = Signal(str)
my_signal = MySignal()
错误代码:
# PySide UI
class MainWindow(QMainWindow):
def __init__(self):
self.bind_signal()
def bind_signal(self):
# 绑定了自定义 Signal 到特定函数,但发送时出错了
my_signal.update_log.connect(self.update_log)
def update_log(self, log_info):
self.ui.log_box.appendPlainText(log_info)
def start_buy(self):
# ControlBuy 是 Selenium 控制类,这里把更新日志的函数传递进去了
# 但 update_log 是直接更新了 UI,所以出现了错误
# 应该是 emit 更新日志的 Signal
control_buy = ControlBuy(self.update_log)
thread = Thread(target=control_buy.start)
thread.start()
正确代码
# emit 更新 log 的 signal,Selenium 直接调用的是 emit signal 的函数
# 而非直接调用 update_log
def send_log_signal(log_info):
my_signal.update_log.emit(log_info)
# PySide UI
class MainWindow(QMainWindow):
def __init__(self):
self.bind_signal()
def bind_signal(self):
my_signal.update_log.connect(self.update_log)
def update_log(self, log_info):
self.ui.log_box.appendPlainText(log_info)
def start_buy(self):
# 传给 Selenium 控制类的是 send_log_signal 而非 self.update_log!!!
control_buy = ControlBuy(send_log_signal)
thread = Thread(target=control_buy.start)
thread.start()
回顾
子线程直接更新 UI,可能导致多个线程同时操作 Pyside 组件,这样就会导致程序崩溃。解决方法就是借助自定义信号更新 UI。
自定义信号的关键是:将自定义信号 connect 到某个函数(在这个函数中更新 UI),在需要更新 UI 的地方,emit 自定义信号(同时传递必要参数)
# 绑定自定义信号到特定函数
my_signal.update_log.connect(self.update_log)
# emit 自定义信号
my_signal.update_log.emit(log_info)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。