PyQt多线程回调未执行求助:线程结束后回调函数未触发
PyQt多线程回调未执行问题排查与修复
问题描述
开发了一个启动5个线程的PyQt程序,每个线程向控制台输出Hello World!,并在线程结束时调用回调函数输出Goodby folks。程序统计每个线程运行时间及总运行时间,总运行时间与最长线程运行时间一致,但5次回调的Goodby folks输出全部缺失。
当前输出
saying Hello World! took 0.126 s saying Hello World! took 0.439 s saying Hello World! took 0.645 s saying Hello World! took 0.994 s saying Hello World! took 1.469 s total runtime is 1.479 s
相关代码
import random import sys import time from typing import Callable from PyQt5.QtCore import QCoreApplication, QThread, QObject, pyqtSignal class MeasureRuntime: def __enter__(self): self.__start = time.perf_counter() return self def __exit__(self, exc_type, exc_val, exc_tb): self.__stop = time.perf_counter() self.elapsed = self.__stop - self.__start return True class MyQtThreads(QObject): def __enter__(self): self.__running_threads: list = [] return self def __exit__(self, exc_type, exc_val, exc_tb): self.__waitForThreadsListEmpty() return True def __waitForThreadsListEmpty(self): while self.__running_threads: for thread in self.__running_threads: if thread.isFinished(): self.__running_threads.pop(self.__running_threads.index(thread)) def doInThread(self, fn, *args, callback: Callable = None, **kwargs) -> int: thread = MyQtThread(fn, *args, **kwargs) if callback: thread.SIG_FINISHED.connect(callback) self.__running_threads.append(thread) thread.start() return id(thread) class MyQtThread(QThread): SIG_FINISHED = pyqtSignal(object) def __init__(self, fn, *args, **kwargs): super().__init__() self.fn = fn self.args = args self.kwargs = kwargs def run(self): ret = self.fn(*self.args, **self.kwargs) result_info = {"thread_id": id(self), "args": ret} self.SIG_FINISHED.emit(result_info) def say_hello(): with MeasureRuntime() as rt: random.seed() time.sleep(random.random()*3) print(f'saying Hello World! took ', end='') print(f'{rt.elapsed:.3f} s') def say_adieu(args): print('Goodby folks') class Main(QThread): def __init__(self): super().__init__() def run(self): print() with MeasureRuntime() as rt: with MyQtThreads() as threads: for _ in range(5): threads.doInThread(say_hello, callback=say_adieu) print(f'\ntotal runtime is {rt.elapsed:.3f} s') if __name__ == '__main__': qcoreapp = QCoreApplication(sys.argv) thread = Main() thread.finished.connect(qcoreapp.exit) thread.start() qcoreapp.exec()
问题原因
- 信号槽线程上下文错误:
SIG_FINISHED信号在子线程中发射,而say_adieu作为普通函数连接时,Qt默认尝试在子线程执行回调,但子线程没有Qt事件循环,导致信号无法被处理。 - 线程对象提前销毁:
MyQtThread对象是doInThread中的局部变量,线程完成后可能被Python垃圾回收销毁,此时信号还未传递到回调就丢失。 - 线程等待逻辑缺陷:
__waitForThreadsListEmpty方法遍历过程中直接修改列表会导致遍历异常,且未调用thread.wait()确保线程完全结束,仅靠isFinished()判断可能存在线程未完全清理就被移除的情况。
修复方案
1. 修正线程等待逻辑
修改MyQtThreads的__waitForThreadsListEmpty方法,复制列表避免遍历异常,并调用wait()确保线程完全结束:
def __waitForThreadsListEmpty(self): while self.__running_threads: # 复制列表防止遍历中修改原列表引发的问题 for thread in self.__running_threads.copy(): if thread.isFinished(): thread.wait() # 等待线程资源完全释放 self.__running_threads.remove(thread)
2. 指定队列连接信号槽
在连接信号时使用Qt::QueuedConnection,让回调在主线程的事件循环中执行,即使子线程销毁也能保证信号被处理:
from PyQt5.QtCore import Qt def doInThread(self, fn, *args, callback: Callable = None, **kwargs) -> int: thread = MyQtThread(fn, *args, **kwargs) if callback: # 指定队列连接,回调将在主线程执行 thread.SIG_FINISHED.connect(callback, Qt.QueuedConnection) self.__running_threads.append(thread) thread.start() return id(thread)
3. 补充函数返回值(可选)
say_hello无返回值,导致result_info["args"]为None,补充返回值让逻辑更完整:
def say_hello(): with MeasureRuntime() as rt: random.seed() time.sleep(random.random()*3) print(f'saying Hello World! took ', end='') elapsed = rt.elapsed print(f'{elapsed:.3f} s') return elapsed
修复后输出示例
saying Hello World! took 0.126 s Goodby folks saying Hello World! took 0.439 s Goodby folks saying Hello World! took 0.645 s Goodby folks saying Hello World! took 0.994 s Goodby folks saying Hello World! took 1.469 s Goodby folks total runtime is 1.479 s
内容的提问来源于stack exchange,提问作者Holger
相关产品推荐
相关产品推荐

