我在Python3和PyQt5中编写了一个简单的时间跟踪应用程序。时间在单独的线程中跟踪。该线程正在运行的功能无法访问GUI代码。在Windows10应用程序尝试关闭后冻结。这是由致电thread.join()
引起的。我需要在任务管理器中结束该过程以关闭它。在Linux Mint上它运行正常。我使用线程库中的线程。它也不适用于QThread。如果我将thread.join()
行注释掉它没有问题,但此线程运行的代码无法完成。
在Window类的__init__()
函数中初始化线程。
self.trackingThread = Thread(target = self.track)
负责跟踪时间的功能:
def track(self):
startTime = time()
lastWindowChangeTime = startTime
while self.running:
# check if active window has changed
if self.active_window_name != get_active_window_name():
if self.active_window_name in self.applications_time:
self.applications_time[self.active_window_name] += int(time() - lastWindowChangeTime) // 60 # time in minutes)
else:
self.applications_time[self.active_window_name] = int(time() - lastWindowChangeTime) // 60 # time in minutes
lastWindowChangeTime = time()
self.active_window_name = get_active_window_name()
totalTime = int(time() - startTime) // 60 # time in minutes
if date.today() in self.daily_time:
self.daily_time[date.today()] += totalTime
else:
self.daily_time[date.today()] = totalTime
加入主题:
def saveAndQuit(self):
self.running = False
self.trackingThread.join() # the line that's causing application freeze
self.save()
QApplication.instance().quit()
编辑: 例: https://pastebin.com/vt3BfKJL
相关代码:
def get_active_window_name():
active_window_name = ''
if system() == 'Linux':
active_window_name = check_output(['xdotool', 'getactivewindow', 'getwindowname']).decode('utf-8')
elif system() == 'Windows':
window = GetForegroundWindow()
active_window_name = GetWindowText(window)
return active_window_name
EDIT2: 删除这两行app关闭后没有任何问题。除了win32gui之外,还有其他方法可以在Windows上获取活动窗口名称吗?:
window = GetForegroundWindow()
active_window_name = GetWindowText(window)
答案 0 :(得分:0)
我遇到了类似的问题,SO上有人建议我使用这样的东西:
class MyThread(QThread):
def __init__(self):
super().__init__()
# initialize your thread, use arguments in the constructor if needed
def __del__(self):
self.wait()
def run(self):
pass # Do whatever you need here
def run_qt_app():
my_thread = MyThread()
my_thread.start()
qt_app = QApplication(sys.argv)
qt_app.aboutToQuit.connect(my_thread.terminate)
# Setup your window here
return qt_app.exec_()
对我来说很好,只要qt_app启动,my_thread就会运行,并且完成退出后的工作。
编辑:拼写错误
答案 1 :(得分:0)
出现此问题是因为GetWindowText()
正在阻止,因此您的线程永远无法加入。要了解原因,我们必须深入研究win32 documentation
如果目标窗口由当前进程拥有,则GetWindowText会将WM_GETTEXT消息发送到指定的窗口或控件。如果目标窗口由另一个进程拥有并且具有标题,则GetWindowText将检索窗口标题文本。如果窗口没有标题,则返回值为空字符串。此行为是设计使然。如果拥有目标窗口的进程没有响应,它允许应用程序调用GetWindowText而不会无响应。 但是,如果目标窗口没有响应并且它属于调用应用程序,则GetWindowText将导致调用应用程序无响应。
您正在尝试从Qt事件循环调用的函数(saveAndQuit
)中加入线程。因此,在此函数返回之前,Qt事件循环不会处理任何消息。这意味着在另一个线程中对GetWindowText
的调用已经向Qt事件循环发送了一条消息,该消息在saveAndQuit
完成之前不会被处理。但是,saveAndQuit
正在等待线程完成,所以你有一个死锁!
有几种方法可以解决死锁,可能最容易实现的是从Qt事件循环中以超时方式递归调用join
。它有点" hacky",但其他选择意味着改变你的线程行为或使用QThreads的方式。
因此,我会修改您的saveAndQuit
,如下所示:
def saveAndQuit(self):
self.running = False
self.trackingThread.join(timeout=0.05)
# if thread is still alive, return control to the Qt event loop
# and rerun this function in 50 milliseconds
if self.trackingThread.is_alive():
QTimer.singleShot(50, self.saveAndQuit)
return
# if the thread has ended, then save and quit!
else:
self.save()
QApplication.instance().quit()